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);
}
- RememberMeAuthenticationFilter의 doFilter()에 요청이 도착한다.
- 현재 인증된 객체가 없어야 인증을 시작한다
(SecurityContextHolder.getContext().getAuthentication() != null)- 세션이 동작하는 경우, 세션 필터에 의해 JSESSIONID 쿠키가 동작하여 먼저 인증되어서 rememberme는 동작하지 않는다.
세션 기능을 끌라면 http.sessionManagement().disable()를 사용
- 실제 사용자 인증은 RememberMeServices 인터페이스 구현체를 통해 처리된다.
TokenBasedRememberMeServices와PersistentTokenBasedRememberMeServices구현체가 있다TokenBasedRememberMeServices는 메모리에 있는 토큰과 사용자가 request header에 담아서 보낸 토큰을 비교하여 인증을 한다. (기본적으로 14일만 토큰을 유지한다. http.tokenValiditySeconds(seconds)로 변경 가능 )PersistentTokenBasedRememberMeServices는 DB에 저장된 토큰과 사용자가 request header에 담아서 보낸 토큰을 비교하여 인증을 한다. (영구적인 방법)
- RememberMeServices.authLogin(request, response)를 통해 인증과정이 진행된다.
- request로부터 rememberMeCookie를 가져온다.
extractRememberMeCookie(request)- rememberMeCookie가 없는경우 그냥 다음 필터로 넘어간다.
- 쿠키의 길이가 0인 경우에도 다음 필터로 넘어가는데, 지정되어 있던 쿠키를 삭제한다
- 쿠키가 존재한다면 decode한다 (
decodeCookie(rememberMeCookie))- 일반적으로 Base64로 인코딩 되어있으며 다음과 같다
- 인코딩 된 값 : dXNlcjoxNjcxMzU3MDQyMjE3OmU4ZGQ3MDUwYjYyMjk1M2E1ZWE3OTcxYTljNTczNzQ1
- 디코딩 한 값 : user:1671357042217:e8dd7050b622953a5ea7971a9c573745
- userId, 만료시간, signatureValue
- 일반적으로 Base64로 인코딩 되어있으며 다음과 같다
- TokenBasedRememberMeService에게 인증을 위임하여 processAutoLoginCookie() 메소드를 통해 인증을 진행한다.
- 파싱하여 token 으로 쪼갠 길이는 무조건 3이여야 한다
- 토큰으로부터 토큰 만료시간을 가져온다
- UserDetailsService로부터 loadUserByUserName(username) 으로 유저 정보를 가져온다
- 시큐리티 Config에서 UserDetailsService를 설정하지 않으면 default UserDetailsService는 InemoryUserDetailsManager이다
- signatureValue를 검증한다.
- signatureValue는
username + ":" + tokenExpiryTime + ":" + password + ":" + getKey();형태로 MD5 해시 알고리즘을 이용하여 만들어진다. - 즉, signatureValue의 형식은 username, 만료시간, 비밀번호, 키 값인데 이 값들이 다르면 signatureValue값도 달라지니까 이걸로 검증할 수 있는것이다
- signatureValue가 다르면 예외를 던져 RememberMe 인증을 중지한다
- signatureValue는
- 정상적으로 UserDetails 객체가 반환되면 createSuccessfulAuthentication()을 통해 Authentication 객체를 반환한다.
- RememberMeAuthenticationToken (Authentication 구현체)이다.
- 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 |