Spring Security : Remember Me 인증

    이 게시글은 인프런 정수원님의 강의를 복습하며 작성한 글입니다.

    Remember Me 기능

    로그인을 한 사용자가 있더라도 세션이 만료되면 다시 한번 로그인을 해야한다. 그리고 혹시, 오늘 저녁에 자고 일어나서 다음 날 접속했을 때, 바로 접속하면 베스트가 아닐까? 이런 기능을 구현해주는 것을 Remember Me라고 한다. 

    Remember me 기능이 활성화 되면, Remember me 쿠키를 내려준다. 사용자가 요청할 때, Remember me 쿠키를 서버에게 전달해주면 다시 한번 로그인을 하지 않더라도 자동으로 로그인을 할 수가 있게 된다. 왜냐하면 Remember me 쿠키를 만들 던 시점은 로그인을 하던 시점이기 떄문이다. 로그인을 하기 때문에 인증 객체가 만들어진다. 그리고 이런 정보가 Remember Me에 저장되기 때문에 Remember me 쿠키를 통해서 세션이 만료되었더라도 손쉽게 접속할 수 있게 된다. 

     

    Remember me 기능을 간략히 정리하면 다음과 같다.

    • 세션이 만료되고 웹 브라우저가 종료된 후에도 어플리케이션이 사용자를 기억한다.
    • Remember-Me 쿠키를 받으면, 인증 객체를 기반으로 인증하고, 인증이 완료되면 사용자는 로그인 처리가 됨. 

     

    Remember Me 쿠키는 어떻게 동작할까?

    • 인증 성공(Remember-Me 쿠키 설정) → 쿠키 발급. 
    • 인증 실패(Remember-Me 쿠키 존재하면 쿠키 무효화) → 쿠키 무효화
    • 로그 아웃(쿠키가 존재하면 쿠키 무효화) → 쿠키 무효화 

     

    Remember-Me API + Filter 설정

    • rememberMe() : 리멤버미 기능이 활성화 됨. 
    • rememberMeParameter() : 기본 파라미터명은 remember-me → HTML-Form의 체크박스 이름과 맞춰야함.
    • tokenValiditySeconds() : 리멤버미 만료 시간(Default는 14일)
    • alwaysRemember() : 리멤버 미 기능이 활성화 되지 않아도 항상 실행하는 것 여부
    • userDetatilsService() : 리멤버 미 기능을 수행할 때, 시스템에 있는 사용자 계정을 조회하는 과정이 있음. 그 때 필요한 클래스가 필수적으로 설정해야함. → Spring Security가 만들어주기 때문에 의존성 주입만 받으면 됨. 

    RememberMe 파라미터에 대한 부가 설명이다. 앞서 이야기한 것처럼 Remember Me의 파라미터는 "remember-me"다. 그리고 화면을 개발자가 임의로 개발한다면 여러 형태로 바뀌게 될 것인데, 이 때 Remember Me를 제대로 사용하기 위해서는 rememberMeParameter() API를 제대로 사용해서 HTML에 표시된 name과 동일하게 맞추어 줘야한다.

     

    userDetailService

    @Configuration
    @EnableWebSecurity
    @RequiredArgsConstructor
    public class BasicSecurityConfig extends WebSecurityConfigurerAdapter {
    
    
        private final UserDetailsService userDetailsService;

    userDetailService는 Spring Security에서 DI를 해준다. 따라서 스프링 빈으로 등록되었다고 가정하고, DI를 받아서 사용하면 된다. 

     

     

     

     

    Remember-Me 설정 시, 응답 헤더 확인

    remember-me 쿠키에 어떤 값이 있는 것을 볼 수 있음.

    개발자 도구를 확인하면, Response Header로 어떤 값이 오는지를 볼 수가 있다. 로그인이 성공했으니 JSESSIONID를 내려주고, 거기에 Remember-Me 기능까지 활성화 되었기 때문에 Remember-me와 관련된 헤더도 함께 내려오는 것을 확인할 수 있다. 

    이 때, 중요한 것은 JSESSIONID가 없어도 Remember-ME 쿠키만 있으면 로그인이 가능하다는 점이다. Remember-ME 쿠키를 헤더에 담아서 보내면, 서버는 Remember-ME 쿠키로 인증 처리를 해준다. 

     

     

    Rememer Me 인증 구조

    Remember Me 인증과 관련되어 가장 먼저 알아야 하는 부분은 현재 "인증객체가 Null인 경우"에만 Remember Me Filter가 동작한다는 것이다. Remember Me라는 것은 인증이 시작되는 지점에서만 동작한다. 그런데 인증객체가 Null인 경우는 지금 인증이 시작되는 경우다. 인증 객체가 존재한다면 이미 인증이 되었기 때문에 다시 인증 객체를 만들 일이 없기 때문이다. 

    또 다른 인증객체가 없는 경우는 세션이 만료되었거나, 컴퓨터를 껏다켜서 세션이 없는 경우다. 이런 경우에는 Security Context에서 인증 객체를 찾을 수 없기 때문에 인증이 필요한 상태가 된다. 어찌되었건 인증 객체가 Null인 경우에만 Remember Me Filter가 동작하게 된다. 

     

    1. Request를 통해 Remember-Me 쿠키가 전달된다. 
    2. 인증객체가 Null인 경우 Remember-Me 쿠키를 통해 인증이 진행된다.
    3. RememberMeService에서는 TokenBasedRememberMeServices / PersistentTokenBasedRememberMeService를 통해서 인증 처리를 시도한다. 이 때, TokenBased는 메모리에서 관리하고 있는 토큰을 바탕으로 처리해주고, Persistent는 DB에 발급한 토큰을 바탕으로 인증 처리를 해준다. 
    4. 만약 토큰 객체가 존재하지 않는다면, 인증 정보가 없는 것으로 확인하고 다음 필터로 이동한다.
    5. 토큰이 존재한다면, 토큰을 바탕으로 정보를 Decode 해준다. Decode Token이 정상인지 확인한다. 
    6. Remember-Me를 통해 추출한 토큰과 서비스에 있는 Token이 같은지 확인한다. 만약에 다르다면 Exception을 발생해주고, 같다면  User 계정이 존재하는지 확인하고 존재한다면 새로운 인증 객체를 만들어 Security Context에 저장해준다. 

     

    Remember-Me 쿠키 생성 확인

    AbstractAuthProcessingFilter

    Remember Me를 설정한 채 로그인을 하게 되면 AbstractAuthProcessingFilter의 successfulAuth 메서드로 이동을 하게 된다. 여기서 rememberMeServices.loginSucess() 메서드를 호출하게 된다. 

    AbstractRememberMeService

    RememberMeService로 넘어오게 되는데 여기서 Remember Me 체크박스가 체크 되었는지 여부를 확인한다. 체크 박스가 안되었다면 Remember Me 쿠키를 만들 필요가 없으니 Return한다. 체크박스를 한 경우 onLoginSucess() 메서드로 넘어간다. 

    TokenBasedRememberMService

    토큰 기반 리멤버미 서비스로 넘어오는데 여기서 먼저 ID, PW를 읽어온다. 

    TokenBasedRememberMService

    그리고 앞서 주입 시켜주었던 UserDetail를 바탕으로 user를 만든다. 

    TokenBasedRememberMService

    그리고 앞에서 가져온 정보를 바탕으로 setCookie() 메서드를 통해서 Response에 만들어진 Remember Me 쿠키를 셋팅해준다. 

     

    Remember-Me 쿠키 인증 확인

    AbstractAuthProcessingFilter

    Remember-Me 기능을 활성화 했을 때, 인증이 정상 처리되면 쿠키에 Remember-Me를 포함해서 보내준다고 했다. 이런 일련의 과정은 AbstractAuthProcessingFilter 내부에 있는 succesfulAuthentication 메서드에 진행이 된다. 이 메서드 안에는 this.rememberMeServices.loginSuccess() 메서드가 있는데, 이 메서드를 통해서 Remember-Me 쿠키를 만들어주는 것이다. 

    RememberMeFilter

    만약 로그인이 되어있지 않고, Remember-me 헤더만 오는 경우 rememberMeSerivces.autoLogin을 통해서 로그인 인증을 실행한다.

    TokenBasedRememberMeService

    앞서 이야기했듯이 RememberMeService는 여러 RememberMeSerivce를 통해서 로그인 인증 처리를 해준다. TokenBasedRememberMeSerivce를 통해서 메모리에서 관리하고 있는 인증 객체를 바탕으로 인증을 처리해준다. 

    PersistentToeknBasedRememberMeService

    DB에 저장된 토큰값을 바탕으로도 인증 객체를 검증할 수 있는지를 확인해준다. 

    RememberMeAuthenticationFilter

    각종 RememberMe Service를 통해서 인증 객체를 불러온 후에 다시 RememberMeAuthFilter로 돌아오게 된다. 돌아온 후 authenicationManager를 통해서 인증을 진행하게 된다. 앞서 로그인 인증한 것과 동일하게 authenicationManager는 auth Provider에게 값을 전달해서 내부적으로 For문을 돌며 인증이 가능한지 확인하고, 결과가 있을 경우 인증 객체를 만들어서 반환해준다. 

    RememberMeAuthenticationFilter

    RememberMe 쿠키로부터 불러온 인증 객체를 담기 위해 SecurityContext를 하나 만든다. 그리고 Security Context에 인증객체를 넣어주고, 이 Security Context를 SecurityContextHolder에 잘 넣어준다. 

    RememberMeAuthenticationFilter

    이후, doFilter()를 통해 다음 Filter()로 넘어가는 것을 확인할 수 있다.

     

     

    정리 

    • Remember Me기능을 이용하면, 스프링 시큐리티는 인증 객체 관련 정보를 Remember Me 쿠키에 저장한 후 사용자에게 전달해준다.
    • 사용자는 Remember Me 쿠키가 유효한 동안, JSESSIONID가 없더라도 Remember Me 쿠키만으로 로그인 처리가 가능하다.
    • Remember Me 쿠키는 현재 인증 객체가 Null인 경우에만 동작한다. 그래야지 인증을 할 필요가 있기 때문이다. 
    • Remember Me 쿠키는 AbstractAuthProcessingFilter의 로그인 성공 후 메서드에서 필요 시, 만들어진다. 

    댓글

    Designed by JB FACTORY