Personal Research

SecurityContextHolder

마손리 2023. 5. 18. 00:39

Spring Security의 인증부분을 공부하던 도중 SecurityContextHolder에 관해 의문점이 생겨 포스트를 작성하게 되었다.

 

 

AbstractAuthenticationProcessingFilter 클래스의 메서드

SecurityContextHolder는 위와 같이 Spring Security에서 UsernamePasswordAuthenticationFilter에서 인증작업을 모두 마친 후 UserPasswordAuthenticationTokenSecurityContextHolder에 담아 보관하게 된다. 

 

 

하지만 공부 도중 실습단계에서 구현한 코드에는 해당 내용을 찾을 수 없엇다.

실습 코드

 

 

대신 OncePerRequestFilter를 상속받는 또 다른 Filter에서 SecurityContextHolder를 찾을 수 있었다.

public class JwtVerificationFilter extends OncePerRequestFilter {
    private final JwtTokenizer jwtTokenizer;
    private final CustomAuthorityUtils authorityUtils;

    public JwtVerificationFilter(JwtTokenizer jwtTokenizer, CustomAuthorityUtils authorityUtils) {
        this.jwtTokenizer = jwtTokenizer;
        this.authorityUtils = authorityUtils;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                HttpServletResponse response, 
                                FilterChain filterChain) throws ServletException, IOException {
        try {
            Map<String, Object> claims = verifyJws(request);
            setAuthenticationToContext(claims); // SecurityContextHolder를 처리할 메서드
        } catch (Exception ee) {
            request.setAttribute("exception", ee);
        }

        filterChain.doFilter(request, response);
    }
    ...
    ...
    private void setAuthenticationToContext(Map<String, Object> claims) {
    // 사용자 정보 생성
        String username = (String) claims.get("username");
        List<GrantedAuthority> authorities = 
                authorityUtils.createAuthorities((List<String>)claims.get("roles")); 
        Authentication authentication = 
                new UsernamePasswordAuthenticationToken(username, null, authorities); 
		
        // SecurityContextHolder에 사용자 정보 삽입
        SecurityContextHolder.getContext().setAuthentication(authentication); 
    }
}

UPAF 필터가 아닌 OncePerRequestFilter를 상속받는 JwtVerificationFilter에서 SecurityContextHolder에 사용자 정보를 생성한뒤 인가(Authorization)에 사용하게 된다. 

 

 

그리고 SecurityFilterChain 설정을 통하여 UPAF를 상속받는 JwtAuthenticationFilter의 실행 이후 위 코드인 JwtVerificationFilter가 실행되도록 코드를 작성해 주었다.

@Configuration
public class SecurityConfiguration {
    private final JwtTokenizer jwtTokenizer;
    private final CustomAuthorityUtils authorityUtils;

    public SecurityConfiguration(JwtTokenizer jwtTokenizer, CustomAuthorityUtils authorityUtils) {
        this.jwtTokenizer = jwtTokenizer;
        this.authorityUtils = authorityUtils;
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
        http
        		...
                ...
                .apply(new CustomFilterConfigurer()) 
                .and()
                ...
                ...
                );

        return http.build();
    }

    public class CustomFilterConfigurer extends AbstractHttpConfigurer<CustomFilterConfigurer, HttpSecurity> {
        @Override
        public void configure(HttpSecurity builder) throws Exception { 
            AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class); 

            JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager, jwtTokenizer);
            jwtAuthenticationFilter.setFilterProcessesUrl("/v11/auth/login");  
            jwtAuthenticationFilter.setAuthenticationFailureHandler(new MemberAuthenticationFailureHandler());
            jwtAuthenticationFilter.setAuthenticationSuccessHandler(new MemberAuthenticationSuccessHandler());

            JwtVerificationFilter jwtVerificationFilter = new JwtVerificationFilter(jwtTokenizer, authorityUtils);  

	// UPAF 필터 이후 OncePerRequestFilter를 상속받는 JwtVerificationFilter 실행
            builder.addFilter(jwtAuthenticationFilter) 
                    .addFilterAfter(jwtVerificationFilter, JwtAuthenticationFilter.class);
        }
    }
    ...
    ...
}

 

 

물론 UPAF를 상속받는 필터에 SecurityContextHolder 정보를 생성하고 OncePerRequestFilter를 상속받는 필터에도 동일하게 진행하여도 상관없으나 불필요한 코드 중복이 발생한다. 

 

또한 해당 어플리케이션에선 OncePerRequestFilter를 사용하여 모든 요청에 대한 권한처리가 필수이므로 위와 같이 코드를 작성하면 UPAF를 상속받은 필터에서는 SecurityContextHolder 정보를 생성할 필요가 없어진다. 

 

SecurityContextHolder를 꼭 UPAF에서 UsernamePasswordAuthenticationToken을 넣어줘야 되는 것이 아니라 다음 필터로 넘어가기 전에만 UsernamePasswordAuthenticationToken을 삽입해주면 되는 것이었다.

 

(UsernamePasswordAuthenticationToken는 실제로 GlobalSecurityContextHolderStrategy 클래스의 SecurityContext라는 static 필드에 저장되며 SecurityContext 또한 SecurityContextHolder에 스태틱 필드로써 저장되므로 따로 인스턴스화 해주지 않고 UsernamePasswordAuthenticationToken을 삽입해준다.)

 

'Personal Research' 카테고리의 다른 글

BindException과 MethodArgumentNotValidException  (0) 2023.05.10
Mysql, safe update mode  (0) 2023.03.31
MySQL 서브쿼리  (0) 2023.03.31
실험을 통해 깨닳은 추상화의 중요성  (0) 2023.03.11