Today I Learned

필터에 @Component 등록 시 자동 등록

greatwhite 2024. 6. 28. 15:28

스프링 시큐리티 인 액션 9장 공부 이후 필터에 @Component어노테이션을 걸고 Config 클래스에서 기본 인증 필터 위치에 등록했었다.

@Component // 여기가 문제
public class StaticKeyAuthenticationFilter implements Filter {
    private String authorizationKey;

    public StaticKeyAuthenticationFilter(@Value("${authorization.key}") String authorizationKey) {
        this.authorizationKey = authorizationKey;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws IOException, ServletException {
        var httpRequest = (HttpServletRequest) request;
        var httpResponse = (HttpServletResponse) response;

        String authentication = httpRequest.getHeader("Authorization");

        if (authentication == null || !authentication.equals(authorizationKey)) {
            httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return;
        }
        filterChain.doFilter(request, response);
    }
}
@Configuration
public class ProjectConfig {
    private static final Logger log = LoggerFactory.getLogger(ProjectConfig.class);
    private AuthenticationProviderService authenticationProvider;

    @Autowired
    public ProjectConfig(@Lazy AuthenticationProviderService authenticationProvider,
                                               StaticKeyAuthenticationFilter staticKeyAuthenticationFilter) {
                this.staticKeyAuthenticationFilter = staticKeyAuthenticationFilter;
        this.authenticationProvider = authenticationProvider;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        Map<String, PasswordEncoder> encoderMap = new HashMap<>();
        encoderMap.put("bcrypt", new BCryptPasswordEncoder());
        encoderMap.put("scrypt", SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8());
        return new DelegatingPasswordEncoder("bcrypt", encoderMap);
    }

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            **.addFilterAt(staticKeyAuthenticationFilter, BasicAuthenticationFilter.class)**
            .authorizeHttpRequests(
                    request -> request.anyRequest()
                            .permitAll()
            )
            .csrf(Customizer.withDefaults());

        return http.build();
    }
}

10장을 공부하면서 다른 필터를 추가하게 되었고 이 과정에서 Config 클래스에서 addFilterAt() 메서드와 필드에서 StaticKeyAuthenticationFilter를 제거해줬다. 그 뒤 다른 필터를 테스트하였다.

 

책에서는 멀쩡하게 통신이 되었지만, 내 코드에서 추가한 필터는 동작하지만 HTTP 401 Unauthorized를 반환했다. 어디가 문제인지 하나씩 제거하다보니 @Component가 문제인 것을 알게되었고 검색해보니 아래와 같은 내용이 있었다.

 

스프링 부트에 필터를 '조심해서' 사용하는 두 가지 방법

읽고 로깅해봤는데 아마 11단계에서 StaticKeyAuthenticationFilter가 호출되는 과정에서 Authorization헤더가 없어 문제가 발생한 줄 알았다.

curl -v -H "Authorzation:auth_123" http://localhost:8080/hello

...
< HTTP/1.1 401
...

위와 같이 Authorization 헤더와 키값을 넣어줬는데도 401이 발생했다. 혹시나 해서 아래와 같이 이전에 사용했던 Request-Id 헤더도 추가해줬더니 정상적으로 응답이 온다.

curl -v -H "Authorization:auth_123" -H "Request-Id:12345" http://localhost:8080/hello

...
< HTTP/1.1 200
...
GET Hello!

하지만 작성된 Config 클래스에는 위의 두 필터를 등록하는 코드가 없다. 그럼에도 불구하고 영향을 미치고 있다.

@Configuration
public class ProjectConfig {
    private static final Logger log = LoggerFactory.getLogger(ProjectConfig.class);
    private AuthenticationProviderService authenticationProvider;
    @Autowired
    public ProjectConfig(@Lazy AuthenticationProviderService authenticationProvider) {

        this.authenticationProvider = authenticationProvider;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        Map<String, PasswordEncoder> encoderMap = new HashMap<>();
        encoderMap.put("bcrypt", new BCryptPasswordEncoder());
        encoderMap.put("scrypt", SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8());
        return new DelegatingPasswordEncoder("bcrypt", encoderMap);
    }

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
//                .authenticationProvider(authenticationProvider)
//                .formLogin(
//                        customizer -> customizer
//                                .defaultSuccessUrl("/hello", true))
//                .httpBasic(Customizer.withDefaults())
                .addFilterAfter(new CsrfTokenLogger(), CsrfFilter.class)
                .authorizeHttpRequests(
                        request -> request.anyRequest()
                                .permitAll()
                )
                .csrf(Customizer.withDefaults());

        return http.build();
    }
}

정확히 어떻게 돌아가는지는 잘 모르겠지만 @Component 어노테이션이 붙은 StaticKeyAuthenticationFilter가 빈으로 등록되면서 RequestValidationFilter 역시 영향을 받아 함께 등록된 것 같다.

 

@Component 어노테이션을 제거하면 정상적으로 응답에 GET Hello!가 잘 도착한다.

curl -v http://localhost:8080/hello

...
< HTTP/1.1 200
...
GET Hello!

필터 클래스에 @Component를 붙이는 것은 주의해야 할 것 같고, 필요없는 필터 클래스 역시 생성하지 않는 것이 좋을 것 같다.