ysk(0soo)
Lifealong
ysk(0soo)
전체 방문자
오늘
어제
  • 분류 전체보기 (238)
    • Java (50)
      • whiteship-java-study (11)
      • Java (28)
      • time (6)
    • Spring (68)
      • JPA (15)
      • Spring (1)
      • SpringBoot (1)
      • SpringMVC (6)
      • Spring Security (22)
      • Jdbc (1)
      • RestDocs (14)
      • log (6)
    • Kotlin (3)
    • Web (2)
      • nginx (1)
    • Database (14)
      • MySQL (5)
      • PostgreSQL (1)
      • SQL (1)
      • Redis (4)
    • C, C++ (0)
    • Git (1)
    • Docker (2)
    • Cloud (3)
      • AWS (3)
    • 도서, 강의 (0)
      • t5 (0)
    • 기타 (7)
      • 프로그래밍 (1)
    • 끄적끄적 (0)
    • CS (14)
      • 운영체제(OS) (2)
      • 자료구조(Data Structure) (9)
    • 하루한개 (12)
      • 우아한 테크코스-10분테코톡 (12)
    • 스터디 (12)
      • 클린 아키텍처- 로버트마틴 (2)
      • JPA 프로그래밍 스터디 (10)
    • 테스트 (34)
      • JUnit (19)
      • nGrinder (2)
      • JMeter (0)
    • Infra (3)
    • 프로그래머스 백엔드 데브코스 3기 (0)
    • 디자인 패턴 (3)
    • Issue (4)
    • system (1)
      • grafana (0)
      • Prometheus (0)

블로그 메뉴

  • 홈
  • 태그
  • 방명록
  • github

공지사항

인기 글

태그

  • tree
  • FilterSecurityInterceptor
  • 가상 스레드
  • jpa
  • 동등성
  • VirtualThread Springboot
  • AccessDecisionManager
  • LocalDateTime
  • StructuredConcorrency
  • querydsl
  • AuthenticationException
  • restdocs enum
  • mysql
  • scope value
  • restdocs custom
  • UserDetailsService
  • 인가(Authorization) 처리
  • 동일성
  • 동시성 제어
  • java
  • junit5
  • DataJpaTest
  • node exporter basic auth
  • 구조화된 동시성
  • nGrinder
  • AccessDecisionVoter 커스텀
  • nginx basic auth
  • 정규표현식
  • 트랜잭션
  • 가상 스레드 예외 핸들링

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
ysk(0soo)

Lifealong

Spring/Spring Security

Remember-ME 인증과정

2022. 12. 18. 19:34

Remember-ME?

세션이 만료되고 웹 브라우저가 종료된 후에도 어플리케이션이 사용자를 기억하는기능이다.

보통 로그인 창에는 체크박스로 로그인 기억(로그인 저장) 등을 지원하는데, 사용자는 일반적으로 로그인 후에 서버에서 Remember-me 관련 쿠키로(cookie) 보내주면 다음 요청시, 명시적으로 로그아웃 하지 않는이상 계속 로그인 되있는 것처럼 쿠키 기반으로 인증을 하는 방식이다.

 

Remember-Me가 무엇인지 정리하자면 다음과 같다

  • 일반적으로 로그인 한 후에 브라우저를 끄거나, 일정 시간이 지나 세션이 만료되는 경우 명시적으로 로그아웃 하지 않더라도 로그아웃이 된다.
  • 이 때 재 로그인 하지 않더라도 세션이 만료되고 웹 브라우저가 종료된 후에도 어플리케이션이 사용자를 기억하는기능이다.
  • 인증되지 않은 사용자의 HTTP 요청이 remember-me 쿠키(Cookie)를 갖고 있다면, 사용자를 자동으로 인증처리한다

 

페이지에 접속하면 서버에서 세션이 생성되고 웹 브라우저 쿠키에 세션 아이디 정보(JSessionID)가 담긴다.
로그인을 하면 서버는 해당 세션을 인증된 세션으로 취급.

JSESSIONID 쿠키가 동작하는 경우, 세션 필터에 의해 먼저 인증되어서 rememberme는 동작하지 않는다.

세션 기능을 끌라면 http.sessionManagement().disable() 를 사용 하자

 

Remember-Me 기능 활성화

일반적으로 RememberMeAuthenticationFilter가 처리한다.

SecurityConfig에서 기능을 활성화 하지 않으면, RememberMeAuthenticationFilter가 filter chain에 등록되지 않는다

  • 또한, DefaultLoginPageGeneratingFilter에서도 체크박스를 만들지 않는다.

 

SecurityConfig Remember-me 활성화

@Configuration
@EnableWebSecurity
public class WebSecurityConfigure {

    private final Logger log = LoggerFactory.getLogger(WebSecurityConfigure.class);

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

      http
        ...생략
        .rememberMe()
        .rememberMeParameter(“remember”) // 기본 파라미터명은 remember-me 
        .tokenValiditySeconds(3600) // Default 는 14일        
        .alwaysRemember(true) // 리멤버 미 기능이 활성화되지 않아도 항상 실행        
        .userDetailsService(userDetailsService)
    }

}
  • rememberMe() : 리멤버 미 활성화, 설정 시작
  • rememberMeParameter(파라미터 명) : RememberMe 쿠키의 이름을 지정한다. default Name은 remember-me 이다.
    • RememberMe 쿠키 이름은 AbstractRememberMeServices클래스에 default로 정의 되어 있으며, 이 메소드로 파라미터 이름을 바꾸게 된다면 내부적으로 쿠키 이름이 바뀌게 된다
  • tokenValiditySeconds(seconds) : 초 단위로 RememberMe 쿠키의 유효 기간을 정한다. default는 14일 (3600초) 이다
  • alwaysRemember(boolean) : remember-me 매개변수가 설정되지 않은 경우에도 쿠키를 항상 생성해야 하는지 여부
    • default는 false이다
  • userDetailsService(userDetailsService 구현체) : Remember-Me 토큰이 유효한 경우 User를 조회하는데 사용되는 UserDetailsService를 지정한다. 설정이 없다면 기본으로 InMemoryUserDetailsManager를 사용한다.
    • default : InMemoryUserDetailsManager. (UserDetailsService 빈이 필요.)
    • TokenBasedRememberMeServices.processAutoLoginCookie() 에서 loadUserByUserName으로 사용자 정보를 가져오기 때문이다.

 

화면을 개발자가 임의로 개발한다면 여러 형태로 바뀌게 될 것인데, 이 때 Remember Me를 제대로 사용하기 위해서는 rememberMeParameter() 메소드의 파라미터와 HTML 요소의 name과 동일하게 맞추어 줘야한다.

  • ex) <input type="checkbox" name="remember">

 

Remember-Me의 쿠키의 Life Cycle

  • 인증 성공 : 사용자가 로그인 기억 체크박스를 체크하고, 인증에 성공하면 Remeber-Me쿠키를 헤더에 설정 (Remember-Me쿠키 설정)
  • 인증 실패 : 로그인에 실패하거나 쿠키가 time-out 된 경우(쿠키가 존재하면 쿠키 무효화)
    • 즉, 로그인이 성공했어도 사용자가 임의로 로그인 페이지로 돌아간 후 인증에 실패하면, 있는 쿠키도 무효화 시킨다.
  • 로그아웃 : 로그아웃시 (쿠키가 존재하면 쿠키 무효화)
  • 만료시간이 지날 경우에도 무효화

Remember-Me 인증과정

먼저, RememberMeAuthenticationFilter에서 인증이 진행되는데 이 필터가 존재하려면 Remember-Me 기능이 활성화 되어야 한다.

  • SecuritiyConfig 설정

 

RememberMeAuthenticationFilter는 default로 DefaultLoginPageGeneratingFilter나 UsernamePasswordAuthenticationFilter보다 뒤에 존재한다

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        if (SecurityContextHolder.getContext().getAuthentication() != null) {
            this.logger.debug(LogMessage
                    .of(() -> "SecurityContextHolder not populated with remember-me token, as it already contained: '"
                            + SecurityContextHolder.getContext().getAuthentication() + "'"));
            chain.doFilter(request, response);
            return;
        }
        Authentication rememberMeAuth = this.rememberMeServices.autoLogin(request, response);
        if (rememberMeAuth != null) {
            // Attempt authenticaton via AuthenticationManager
            try {
                rememberMeAuth = this.authenticationManager.authenticate(rememberMeAuth);
                // Store to SecurityContextHolder
                SecurityContext context = SecurityContextHolder.createEmptyContext();
                context.setAuthentication(rememberMeAuth);
                SecurityContextHolder.setContext(context);
                onSuccessfulAuthentication(request, response, rememberMeAuth);
                this.logger.debug(LogMessage.of(() -> "SecurityContextHolder populated with remember-me token: '"
                        + SecurityContextHolder.getContext().getAuthentication() + "'"));
                this.securityContextRepository.saveContext(context, request, response);
                if (this.eventPublisher != null) {
                    this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
                            SecurityContextHolder.getContext().getAuthentication(), this.getClass()));
                }
                if (this.successHandler != null) {
                    this.successHandler.onAuthenticationSuccess(request, response, rememberMeAuth);
                    return;
                }
            }
            catch (AuthenticationException ex) {
                this.logger.debug(LogMessage
                        .format("SecurityContextHolder not populated with remember-me token, as AuthenticationManager "
                                + "rejected Authentication returned by RememberMeServices: '%s'; "
                                + "invalidating remember-me token", rememberMeAuth),
                        ex);
                this.rememberMeServices.loginFail(request, response);
                onUnsuccessfulAuthentication(request, response, ex);
            }
        }
        chain.doFilter(request, response);
}
  1. RememberMeAuthenticationFilter의 doFilter()에 요청이 도착한다.
  2. 현재 인증된 객체가 없어야 인증을 시작한다 (SecurityContextHolder.getContext().getAuthentication() != null)
    • 세션이 동작하는 경우, 세션 필터에 의해 JSESSIONID 쿠키가 동작하여 먼저 인증되어서 rememberme는 동작하지 않는다.
    • 세션 기능을 끌라면 http.sessionManagement().disable() 를 사용
  3. 실제 사용자 인증은 RememberMeServices 인터페이스 구현체를 통해 처리된다.
    • TokenBasedRememberMeServices와 PersistentTokenBasedRememberMeServices 구현체가 있다
    • TokenBasedRememberMeServices는 메모리에 있는 토큰과 사용자가 request header에 담아서 보낸 토큰을 비교하여 인증을 한다. (기본적으로 14일만 토큰을 유지한다. http.tokenValiditySeconds(seconds)로 변경 가능 )
    • PersistentTokenBasedRememberMeServices는 DB에 저장된 토큰과 사용자가 request header에 담아서 보낸 토큰을 비교하여 인증을 한다. (영구적인 방법)
  4. RememberMeServices.authLogin(request, response)를 통해 인증과정이 진행된다.
  5. request로부터 rememberMeCookie를 가져온다. extractRememberMeCookie(request)
    • rememberMeCookie가 없는경우 그냥 다음 필터로 넘어간다.
    • 쿠키의 길이가 0인 경우에도 다음 필터로 넘어가는데, 지정되어 있던 쿠키를 삭제한다
  6. 쿠키가 존재한다면 decode한다 (decodeCookie(rememberMeCookie))
    • 일반적으로 Base64로 인코딩 되어있으며 다음과 같다
      • 인코딩 된 값 : dXNlcjoxNjcxMzU3MDQyMjE3OmU4ZGQ3MDUwYjYyMjk1M2E1ZWE3OTcxYTljNTczNzQ1
      • 디코딩 한 값 : user:1671357042217:e8dd7050b622953a5ea7971a9c573745
        • userId, 만료시간, signatureValue
  7. TokenBasedRememberMeService에게 인증을 위임하여 processAutoLoginCookie() 메소드를 통해 인증을 진행한다.
    • 파싱하여 token 으로 쪼갠 길이는 무조건 3이여야 한다
    • 토큰으로부터 토큰 만료시간을 가져온다
  8. UserDetailsService로부터 loadUserByUserName(username) 으로 유저 정보를 가져온다
    • 시큐리티 Config에서 UserDetailsService를 설정하지 않으면 default UserDetailsService는 InemoryUserDetailsManager이다
  9. signatureValue를 검증한다.
    • signatureValue는 username + ":" + tokenExpiryTime + ":" + password + ":" + getKey(); 형태로 MD5 해시 알고리즘을 이용하여 만들어진다.
    • 즉, signatureValue의 형식은 username, 만료시간, 비밀번호, 키 값인데 이 값들이 다르면 signatureValue값도 달라지니까 이걸로 검증할 수 있는것이다
    • signatureValue가 다르면 예외를 던져 RememberMe 인증을 중지한다
  10. 정상적으로 UserDetails 객체가 반환되면 createSuccessfulAuthentication()을 통해 Authentication 객체를 반환한다.
    • RememberMeAuthenticationToken (Authentication 구현체)이다.
  11. Authentication 객체를 AuthenticationManager(ProviderManager, RememberMeAuthenticationProvider를 사용한다.) 에게 인증처리를 위임하고 SecurityContext에 저장하고 리턴한다.

 

참조

  • 인프런 정수원님 스프링 시큐리티 강의
저작자표시 비영리

'Spring > Spring Security' 카테고리의 다른 글

SecurityContextPersistenceFilter - SecurityContext 영속화 필터, SecurityContextRepository  (0) 2022.12.18
RememberMeAuthenticationFilter, RememberMeSevices  (0) 2022.12.18
BasicAuthenticationFilter  (0) 2022.12.18
AbstractAuthenticationProcessingFilter (a.k.a UsernamePasswordAuthenticationFilter) 와 인증과정  (0) 2022.12.18
DefaultLogoutPageGeneratingFilter, LogOutFilter  (0) 2022.12.17
    'Spring/Spring Security' 카테고리의 다른 글
    • SecurityContextPersistenceFilter - SecurityContext 영속화 필터, SecurityContextRepository
    • RememberMeAuthenticationFilter, RememberMeSevices
    • BasicAuthenticationFilter
    • AbstractAuthenticationProcessingFilter (a.k.a UsernamePasswordAuthenticationFilter) 와 인증과정
    ysk(0soo)
    ysk(0soo)
    백엔드 개발을 좋아합니다. java kotlin spring, infra 에 관심이 많습니다. email : kim206gh@naver.com github : https://github.com/devysk

    티스토리툴바