Spring Security 5 버전에서 Spring Security 6 버전으로 올라가면서 변경된 SecurityContext 에 인증 객체 설정
Spring Security 5 버전에서 6버전으로 올라가면서 Success Handler 까지 정상적으로 인증이 성공했지만
로그인이 안되어 찾아보다가 공식 문서 및 구글링을 통해 찾았다.
위 내용을 요약하면 아래와 같다.
Spring Security 5 에서는 SecurityContextPersistenceFilter 를 사용하여 Security Context 가
SecurityContextRepository 에 자동으로 저장된다고 한다. 정상적인 요청과 응답이 수행되기 전에
세션을 생성한다는 문제점과 이러한 세션을 추적하기가 어렵다는 문제가 있었다.
Spring Security 6 에서는 SecurityContextPersistenceFilter 가 Defrecated 되고
SecurityContextHolderFilter 를 사용하여 SecurityContext 를 SecurityContextRepository 로 부터 읽기 동작만을
수행한다. 인증된 세션의 저장은 유저가 직접 명시적으로 저장해야 한다.
SecurityContextPersistenceFilter 내용을 보면 아래와 같다.
SecurityContextRepository 인 this.repo 는 HttpSessionSecurityContextRepository 를 기본 구성 클래스로 사용한다.
SecurityContextPersistenceFilter
SecurityContextPersistenceFilter 의 동작과정을 봐보자
1. 요청이 처리되기 전에 SecurityContextRepository 에서 SecurityContext 를 꺼내와서
SecurityContextHolder에 저장한다.
2. 요청이 처리된다.
3. SecurityContextHolder 에 저장된 값이 바뀌었으면 SecurityContextRepository 에 저장한다.
하지만 SecurityContextPersistenceFilter 의 finally 를 보면 변경여부에 따라 저장하는게 아닌
무조건 저장하는걸 볼 수 있다.
... // SecurityContextPersistenceFilter.class
finally {
SecurityContext contextAfterChainExecution = this.securityContextHolderStrategy.getContext();
// Crucial removal of SecurityContextHolder contents before anything else.
this.securityContextHolderStrategy.clearContext();
this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
request.removeAttribute(FILTER_APPLIED);
this.logger.debug("Cleared SecurityContextHolder to complete request");
}
this.securityContextHolderStrategy 인 ThreadLocalSecurityContextHolderStrategy 에서 SecurityContext 를
불러와 contextAfterChainExecution 변수에 넣는다.
그리고 SecurityContextHolder 의 전략 SecurityContextolderStrategy 의 기본값인 ThreadLocal 방식의 클래스
ThreadLocalSecurityContextHolderStrategy 에서 쓰레드의 SecurityContextHolder 를 초기화한다.
그 후에 SecurityContextRepository 에 설정된 HttpSessionSecurityContextRepository 에
불러온 SecurityContext 인 contextAfterChainExecution 변수를 context 에 재설정한다.
SecurityContextHolderFilter
SecurityContextHolderFilter 는 5 버전에서 사용한 SecurityContextPersistenceFilter 와 비슷하지만
다른점은 3번 과정이 빠졌다.
SecurityContextRepository 에서 SecurityContextHolder 를 불러와 요청을 처리한 후
Authentication 인증 객체를 SecurityContext 에 저장하지 않으므로 로그인이 되지 않았다.
SecurityContextHolderFilter 내부
1. loadDeferredContext(request) 함수를 통해 SecurityContextRepository 에 있는
SecurityContext 를 가져온다.
2. .setDeferredContext 함수를 통해 가져온 SecurityContext 가 있다면 SecurityContextHolder에 저장하고
null 이라면 새로 만들어 저장한다.
Spring Security 6 버전 SecurityContext에 Authentication 인증 객체 설정하기
Spring Security 6 공식 문서에 나온 설정방법은 아래 이미지와 같다.
SecurityContextHolder 에서 SecurityContext 를 가져와 SecurityContexrRepository 에 save 하는 과정을 추가해야한다.
또한 SecurityContext 에 인증된 객체인 Authentication 도 설정해줘야 한다.
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.addFilterAt(authenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.csrf((csrf) -> csrf.disable())
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/auth/**").permitAll()
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/auth/login")
.loginProcessingUrl("/auth/process")
.usernameParameter("user_id")
.passwordParameter("user_pw")
.defaultSuccessUrl("/test", true)
.successHandler(successHandler())
.permitAll()
)
.securityContext(securityContext -> securityContext
.securityContextRepository(new HttpSessionSecurityContextRepository())
);
return http.build();
}
.securityContext(securityContext -> securityContext
.securityContextRepository(new HttpSessionSecurityContextRepository())
);
SeucirytContextRepository 를 HttpSessionSecurityContextRepository 를 사용한다고 설정한다.
아래는 실제로 인증된 객체를 가지고 있는 AuthenticationSuccessHandler Custom 클래스이다.
public class AuthenticationSuccessHandlerCustomImpl implements AuthenticationSuccessHandler {
private final SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder.getContextHolderStrategy();
private final SecurityContextRepository securityContextRepository = new HttpSessionSecurityContextRepository();
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
String redirectUrl = request.getContextPath() + "/test";
SecurityContext securityContext = this.securityContextHolderStrategy.getContext();
securityContext.setAuthentication(authentication);
this.securityContextHolderStrategy.setContext(securityContext);
this.securityContextRepository.saveContext(securityContext, request, response);
response.sendRedirect(redirectUrl);
}
}
위처럼 SecurityContextHolder 에서 SecurityContext 를 불러와 인증 객체 Authentication 을 설정한다.
설정된 SecurityContext 는 SecurityContextHolder 에 설정하고 SecurityContextRepository에
SecurityContext 를 save 한다.
SecurityContextHolderFilter 의 doFilter 메소드에서 finally 부분을 보면 SecurityContextHolderStrategy 를 통해 SecurityContextHolder 를 clearContext()를 하는데 SecurityContext는 Repository 에 저장한 후에
clearContext() 를 하기 때문에 상관없었다...