Java/Spring

Spring Security 5 버전에서 Spring Security 6 버전으로 올라가면서 변경된 SecurityContext 에 인증 객체 설정

B.OCK 2024. 2. 22. 14:46
반응형

Spring Security 5 버전에서 6버전으로 올라가면서 Success Handler 까지 정상적으로 인증이 성공했지만

로그인이 안되어 찾아보다가 공식 문서 및 구글링을 통해 찾았다.

 

https://docs.spring.io/spring-security/reference/6.0/migration/servlet/session-management.html

 

위 내용을 요약하면 아래와 같다.


Spring Security 5 에서는 SecurityContextPersistenceFilter 를 사용하여 Security Context 가 
SecurityContextRepository 에 자동으로 저장된다고 한다. 정상적인 요청과 응답이 수행되기 전에
세션을 생성한다는 문제점과 이러한 세션을 추적하기가 어렵다는 문제가 있었다.

Spring Security 6 에서는 SecurityContextPersistenceFilter 가 Defrecated 되고 
SecurityContextHolderFilter 를 사용하여 SecurityContext 를 SecurityContextRepository 로 부터 읽기 동작만을
수행한다. 인증된 세션의 저장은 유저가 직접 명시적으로 저장해야 한다.

 

 

SecurityContextPersistenceFilter 내용을 보면 아래와 같다.

 

SecurityContextRepository 인 this.repo 는 HttpSessionSecurityContextRepository 를 기본 구성 클래스로 사용한다.

 

SecurityContextPersistenceFilter

SecurityContextPersistenceFilter 의 동작과정을 봐보자

https://docs.spring.io/spring-security/reference/6.0/servlet/authentication/persistence.html#securitycontextholderfilter

 

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번 과정이 빠졌다.

 

https://docs.spring.io/spring-security/reference/6.0/servlet/authentication/persistence.html#securitycontextholderfilter

 

SecurityContextRepository 에서 SecurityContextHolder 를 불러와 요청을 처리한 후 

Authentication 인증 객체를 SecurityContext 에 저장하지 않으므로 로그인이 되지 않았다.

 

 

SecurityContextHolderFilter 내부

 

 

 

1. loadDeferredContext(request) 함수를 통해 SecurityContextRepository 에 있는

SecurityContext 를 가져온다.

 

2. .setDeferredContext 함수를 통해 가져온 SecurityContext 가 있다면 SecurityContextHolder에 저장하고

null 이라면 새로 만들어 저장한다.

 

 

 

 

Spring Security 6 버전 SecurityContext에 Authentication 인증 객체 설정하기

Spring Security 6 공식 문서에 나온 설정방법은 아래 이미지와 같다.

https://docs.spring.io/spring-security/reference/6.0/migration/servlet/session-management.html

 

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() 를 하기 때문에 상관없었다...

반응형