ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring Security Session 인증 흐름
    카테고리 없음 2025. 6. 12. 10:23

    주요 Filter의 역할

    SecurityContextHolderFilter

    필터체인 진입시 세션을 만들고, Context를 생성하는 필터이다.

    UsernamePasswordAuthentcationFilter

    /login(또는 설정한 url)으로 요청시 요청을 가로채 사용자 인증(Authentication)을 처리하는 필터이다.

    기본적으로 username과 password를 통해 인증을하며, 인증 성공시 Context의 authentication에 사용자 정보를 저장한다.

    ExceptionTranlationFilter

    예외를 감지하고, 예외 종류에 따라 응다답을 처리한다.

    만약 인가예외(AccessException)라면 커스텀한 로그인창 또는 403에러를,

    인증예외(AuthenticationException)이라면 설정한(기본은 /login) url로 리다이렉트 시킨다.

    AuthorizationFilter

    필터 체인 마지막에서 요청에 대한 인가(Authirization)를 처리하는 필터이다.

    하지만, 인증(Authentication)이 존재하지 않는 상태라면 인가를 할 수 없기 때문에,
    내부적으로 InsufficientAuthenticationException을 발생시켜 인증 예외로 처리한다.

     

     

    로그인 인증/인가 과정

    로그인 화면 url : /login

    인증 url : /loginProcess

    처음 페이지 진입시 (로그인 화면이 뜨기까지)

    1. 사용자가 인증/인가가 필요한 페이지에 접근을 시도한다.

    2. SCHF(SecurityContextHolderFilter)에서 세션(또는 토큰)의 사용자 정보로 Context를 생성한다.

    하지만 아직 아무런 사용자 정보가 없기 때문에 빈 Contxect만 생성한다.

    3. AF(AuthorizationFilter)에서 인가를 판단한다. 빈 Context이기 때문에, 인가가 아닌 인증실패로 InsufficientAuthenticationException를 발생시킨다.

    4. ETF(ExceptionTranslationFilter)가 Exception을 감지하고 /login 으로 라다이렉트시킨다.

    5. /login으로 요청이 다시 보내지고, SCHF는 다시 Context를 생성한다(빈 Context)

    6. AF는 인가를 판단하는데, /login에 대한 요청은 permitAll()로 설정했기 때문에 Context에 사용자 정보가 없어도 인가를 통과한다.

    7. 로그인 화면창이 뜬다.

     

    로그인창에서 사용자 정보 입력후 로그인 요청시

     

    1. 사용자 정보를 입력후 로그인시, /loginProcess로 요청이 간다.

    2. SCHF는 사용자 정보로 세션을 만들고, 해당 세션으로 Context를 생성한다.

    3. /loginProcess 요청을 UPAF(UsernamePasswordAuthentocationFilter)가 가로채고, ProviderManager -> Provider 를 거쳐 UserDetailService에서 사용자 정보를 DB에서 조회해 인증을 시도한다.

    3-1. 만약 사용자 정보가 조회되지 않을 경우, AuthenticationException을 발생시키고, ETF가 해당 예외를 가로채 /login으로 리다이렉트 시킨다.

    3-2. 사용자 정보가 조회될 경우, Context의 authentication에 사용자 정보를 저장하고(AbstractAuthenticationProccessingFilter의 successfulAuthentication메서드), LoginSuccessHandler의 onAuthenticationSuccess()메서드를 실행

    4. onAuthenticationSuccess() 메서드에서 defaultSeccessUrl로 리다이렉트 시키는데, 설정하지 않을 경우 기본은 root(/)이다.

    5. 이후 필터는 거치지 않고 return되면서 filterchain을 거꾸로 통과하다가, SCHF에서 Context의 정보로 세션을 재생성한다.

    6. 리다이렉트시켰던 root(/)로 다시 요청이 보내진다.

    7. 사용자 정보가 담긴 세션을 통해 Context를 생성한다.

    8. AF에서 Context 정보로 인가를 판단한다.

    8-1. 인가에 실패할 경우, AccessDeniedException을 발생시켜 ETF가 403응답을 보낸다.

    8-2. AF에서 인가를 확인하고, 인가 성공 시 root 컨트롤러로 보내서 메인 화면을 띄운다.

     

     

    질문

    Q. /login, /loginProcess,defaultSeccessUrl은 어디에서 세팅?

    Filterchain 설정할 때, 다음과 같이 설정할 수 있다.

    http.formLogin(it ->
            it.loginPage("/login")
                    .usernameParameter("loginId")
                    .passwordParameter("password")
                    .loginProcessingUrl("/loginProcess")
                    .permitAll()
    );

     

    Q. 초기 진입시에는 UPAF는 통과 안하나?

    UPAF는 설정한 로그인 url(/loginProcess)로 요청할때만 작동된다.  

     

    Q. context holder에 저장되는건 로그인 인증 처리 과정 마지막에서 저장하는거 아닌가? 거기에 이미 저장되어있을텐데 왜 필터 통과할떄 SCHF에서 session으로 다시 contexholder를 세팅하지?

    1. 사용자가 로그인 성공 → Authentication 객체 생성됨
    2. SecurityContextHolder.getContext().setAuthentication(...) 호출됨
    3. 이 인증 정보는 SecurityContextHolderFilter가 응답 끝날 때 세션에 저장

    => SecurityContextHolder는 기본적으로 ThreadLocal 기반이므로 요청이 끝나면 초기화됨 (즉, 비어있음)

    => 다음 요청에서는 SecurityContextHolderFilter가 세션에 저장해두었던 SPRING_SECURITY_CONTEXT를 꺼내서 다시 SecurityContextHolder에 세팅

     

     

    Q. 그럼 처음 SecurityContextHolderFilter 진입시 세션으로 SecurityContext를 생성한 후에, UserDetailService에서 또 Context를 만들면 기존꺼는?

    UserDetailService에서 또 Context 만드는 것이 아니라, getContext 해서 생성된 Context 객체를 꺼내고, 해당 객체의 Authentication을 생성하는 것. getContext.setAuthentication(authentication)

     

     

    Q. 세션은 어디서 만들어지는가?

    SecurityContextHolderFilter 의 아래 코드에 의해 만들어짐

    this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());

     

    saveContext의 구현체는 HttpSessionSecurityContextRepository

    즉, HttpSessionSecurityContextRepository 의 saveContext의

    session = request.getSession(); 에 의해 세션이 생성됨.

     

     

     

Designed by Tistory.