RememberMeAuthenticationFilter

  1. Spring Security RememberMeAuthenticationFilter
    1. 개요
    2. 기본 Spring Security 설정
    3. AbstractAuthenticationProcessingFilter -> successfulAuthentication
    4. RememberMeAuthenticationFilter

Spring Security RememberMeAuthenticationFilter

1.PNG

RememberMe 기능은 해당 사이트 로그인 시 세션 종료 및 브라우저 종료 하더라도 로그인 상태를 유지 할 수 있는 기능이다.

개요

일반적으로 로그인을 했다는 것은 해당 WAS 서버에 해당 사용자의 세션이 생성 되었고

Spring Security 가 인증 객체 통해 보관 하고 있다.

그럼 사용자는 다른 페이지에 접속해도 해당 세션을 request 시 함께 서버로 전달 하고

서버는 Spring Security 가 인증 객체 통해 보관 하고 있는 세션과 맞물려 있는지 체크 한다.

즉 인증 체크를 한다는 것 이다.

그러나 그 세션이 기한이 끝났거나 소멸될 경우 다시 로그인을 해야 하는데

RememberMe 기능은 소멸된 세션이라 할지라도 remember-me 라는 또 다른 세션을 생성 후 클라이언트에 response 한다.

RememberMe로 로그인 성공 시 세션

그럼 일반 세션을 변조 하거나 value 값을 삭제 할 경우

일반 세션 value 변조

이렇게 일반 세션을 변조 하였다. 그럼 일반적으로 해당 세션값은 서버에 등록된 1234 값이 없기 때문에 인증 처리 되지 않아 다시 로그인 해야 한다.

하지만 새로고침 하면 remember-me 세션 토큰값 통해 Spring Security 에는 파싱 해서 아이디 및 비밀번호 다시 추출해

새로운 세션 생성 된다.

다시 인증 처리를 하게 된다.

여기까지 기본 정의를 설명 하였고 본격적으로 Spring Security 에서는 rememberMe 를 어떻게 처리 및 Flow 하는지

알아보겠다.

기본 Spring Security 설정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

... 생략 ...

@Override
protected void configure(HttpSecurity http) throws Exception {

... 생략 ...

http
.rememberMe()
.rememberMeParameter("remember")
.tokenValiditySeconds(3600)
.userDetailsService(userDetailsService);

... 생략 ...

}

... 생략 ...

}

AbstractAuthenticationProcessingFilter -> successfulAuthentication

최종적으로 로그인 인증 처리가 완료 되면 다시 AbstractAuthenticationProcessingFilter 로 돌아 오게 되는데

successfulAuthentication 메소드를 호출 하게 된다.

1
2
3
4
5
6
7
8
9
// AbstractAuthenticationProcessingFilter 클래스
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
.. 생략 ..
SecurityContextHolder.getContext().setAuthentication(authResult);
this.rememberMeServices.loginSuccess(request, response, authResult);


.. 생략 ..
}

rememberMeServices.loginSuccess 호출 해서 해당 remember-me Token 생성 한다.

1
2
3
4
5
6
7
8
// AbstractRememberMeServices 클래스
public final void loginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
if (!this.rememberMeRequested(request, this.parameter)) {
this.logger.debug("Remember-me login not requested.");
} else {
this.onLoginSuccess(request, response, successfulAuthentication);
}
}

onLoginSuccess 메소드를 호출 해서

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// AbstractRememberMeServices 구현체인 TokenBasedRememberMeServices 클래스
public void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
String username = this.retrieveUserName(successfulAuthentication);
String password = this.retrievePassword(successfulAuthentication);
if (!StringUtils.hasLength(username)) {
this.logger.debug("Unable to retrieve username");
} else {
if (!StringUtils.hasLength(password)) {
UserDetails user = this.getUserDetailsService().loadUserByUsername(username);
password = user.getPassword();
if (!StringUtils.hasLength(password)) {
this.logger.debug("Unable to obtain password for user: " + username);
return;
}
}

int tokenLifetime = this.calculateLoginLifetime(request, successfulAuthentication);
long expiryTime = System.currentTimeMillis();
expiryTime += 1000L * (long)(tokenLifetime < 0 ? 1209600 : tokenLifetime);
String signatureValue = this.makeTokenSignature(expiryTime, username, password);
this.setCookie(new String[]{username, Long.toString(expiryTime), signatureValue}, tokenLifetime, request, response);

... 생략 ...
}
}

remember-me token 생성하는 로직이다.

아이디 패스워드 만료시간 등 조합해서 setCookie 하게 되고 response 시 생성한 token 을 클라이언트에게 전달 하게 된다.

RememberMeAuthenticationFilter

출처 : https://qiita.com/opengl-8080/items/7c34053c74448d39e8f5

Spring Security 에서는 RememberMeAuthenticationFilter 이 객체에 전달하기 위해

조건이 있다. 우선 인증된 세션이 서버에 찾지 못 하거나 기간이 만료되거나 NULL 경우

그리고 클라이언트가 request 시 remember-me 쿠키 토큰값을 가지고 오는 경우

RememberMeAuthenticationFilter 객체가 발동 한다.

클라이언트 측에서 세션값을 제거 하거나 조작하고 다시 해당 페이지 새로고침 해보자!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// RememberMeAuthenticationFilter 클래스
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;

// SecurityContext 에 있는 인증 객체가 Null 일때 rememberMe Flow 하게 된다.
if (SecurityContextHolder.getContext().getAuthentication() == null) {
Authentication rememberMeAuth = this.rememberMeServices.autoLogin(request, response);
if (rememberMeAuth != null) {
try {
rememberMeAuth = this.authenticationManager.authenticate(rememberMeAuth);
SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);
this.onSuccessfulAuthentication(request, response, rememberMeAuth);

.. 생략 ..
}

chain.doFilter(request, response);
} else {
.. 생략 ..
}

}

this.rememberMeServices.autoLogin(request, response);

기존 세션값이 null 이기 때문에 remember-me token 을 이용해 다시 인증 처리 하게 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// AbstractRememberMeServices 클래스
public final Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) {

// 발급 받는 remember-me token 값이 있는 확인 한다.
String rememberMeCookie = this.extractRememberMeCookie(request);

if (rememberMeCookie == null) {
return null;
} else {
... 생략 ...
return this.createSuccessfulAuthentication(request, user);
... 생략 ...
}
}
}

return this.createSuccessfulAuthentication(request, user);

인증 객체 생성 메소스 호출 한다.

1
2
3
4
5
6
// AbstractRememberMeServices 클래스
protected Authentication createSuccessfulAuthentication(HttpServletRequest request, UserDetails user) {
RememberMeAuthenticationToken auth = new RememberMeAuthenticationToken(this.key, user, this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
auth.setDetails(this.authenticationDetailsSource.buildDetails(request));
return auth;
}

여기서 인증 객체를 생성 후 RememberMeAuthenticationFilter 클래스 doFilter 메소드에 반환 해서

1
2
// RememberMeAuthenticationFilter > doFilter
rememberMeAuth = this.authenticationManager.authenticate(rememberMeAuth);

authenticationManager 호출해서 다시 authenticationProvider 인증 처리 위임해 평상시대로 인증 처리를 하게 된다.

1
2
// RememberMeAuthenticationFilter > doFilter
SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);

이상으로 인증이 성공적으로 처리 되었다면 SecurityContext 에 해당 사용자 인증 객체에 저장 하게 된다.


Copyright 2020- syh8088. 무단 전재 및 재배포 금지. 출처 표기 시 인용 가능.

💰

×

Help us with donation