Working with OPTIONS and CORS when using a signed filter instead of a controller

I have AbstractAuthenticationProcessingFilter

one that I am using to handle POST requests on a path /sign-in

. CORS preflight requests return 404 because there is no path that matches. This makes sense to me.

I would like to know if there is a way to tell Spring that there is a filter that handles POST (and not a controller) so that Spring can send OPTIONS the same as if the controller was handling POST. Would it be bad practice to write a controller with one PostMapping

? I'm not sure how this would look since the filter technically handles POST.

Thank you for your help!

Update

Here's my setup. I was originally sent from a phone, so I was unable to add this details. See below. To repeat, there is no controller for /sign-in

. POST is being processed JwtSignInFilter

.

CORS configuration

@EnableWebMvc
@Configuration
public class CorsConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
            .allowedOrigins("*")  // TODO: Lock this down before deploying
            .allowedHeaders("*")
            .allowedMethods(HttpMethod.GET.name(), HttpMethod.POST.name(), HttpMethod.DELETE.name())
            .allowCredentials(true);
    }
}

      

Security configuration

@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public JwtSignInFilter signInFilter() throws Exception {
        return new JwtSignInFilter(
            new AntPathRequestMatcher("/sign-in", HttpMethod.POST.name()),
            authenticationManager()
        );
    }

    @Bean
    public JwtAuthenticationFilter authFilter() {
        return new JwtAuthenticationFilter();
    }

    @Autowired
    private UserDetailsService userDetailsService;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .authorizeRequests()
            .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
            .antMatchers(HttpMethod.POST, "/sign-in").permitAll()
            .anyRequest().authenticated()
            .and()
            .addFilterBefore(
                signInFilter(),
                UsernamePasswordAuthenticationFilter.class
            )
            .addFilterBefore(
                authFilter(),
                UsernamePasswordAuthenticationFilter.class
            );
    }
}

      

Input filter

public class JwtSignInFilter extends AbstractAuthenticationProcessingFilter {

    @Autowired
    private TokenAuthenticationService tokenAuthService;

    public JwtSignInFilter(RequestMatcher requestMatcher, AuthenticationManager authManager) {
        super(requestMatcher);
        setAuthenticationManager(authManager);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException, IOException, ServletException {
        SignInRequest creds = new ObjectMapper().readValue(
            req.getInputStream(),
            SignInRequest.class
        );

        return getAuthenticationManager().authenticate(
            new UsernamePasswordAuthenticationToken(
                creds.getEmail(),
                creds.getPassword(),
                emptyList()
            )
        );
    }

    @Override
    protected void successfulAuthentication(
        HttpServletRequest req,
        HttpServletResponse res, FilterChain chain,
        Authentication auth) throws IOException, ServletException {
        tokenAuthService.addAuthentication(res, auth.getName());
    }
}

      

Authentication filter

public class JwtAuthenticationFilter extends GenericFilterBean {

    @Autowired
    private TokenAuthenticationService tokenAuthService;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        Authentication authentication = tokenAuthService.getAuthentication((HttpServletRequest)request);
        SecurityContextHolder
            .getContext()
            .setAuthentication(authentication);
        filterChain.doFilter(request, response);
    }
}

      

+3


source to share


1 answer


Ok, finally figured out how to fix this. After hours of tinkering and searching, I found that I need to use a filter based CORS configuration and then handle CORS pre-programs (OPTIONS requests) in a signal filter, just returning 200 OK. The CORS filter will then add the appropriate headers.

Updated config below (note that mine is CorsConfig

no longer needed since we have a CORS filter in SecurityConfig

, but JwtAuthenticationFilter

the same as before).

Security configuration



@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("*");  // TODO: lock down before deploying
        config.addAllowedHeader("*");
        config.addExposedHeader(HttpHeaders.AUTHORIZATION);
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }

    @Bean
    public JwtSignInFilter signInFilter() throws Exception {
        return new JwtSignInFilter(
            new AntPathRequestMatcher("/sign-in"),
            authenticationManager()
        );
    }

    @Bean
    public JwtAuthenticationFilter authFilter() {
        return new JwtAuthenticationFilter();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .cors()
            .and()
            .csrf().disable()
            .authorizeRequests()
            .antMatchers("/sign-in").permitAll()
            .anyRequest().authenticated()
            .and()
            .addFilterBefore(
                signInFilter(),
                UsernamePasswordAuthenticationFilter.class
            )
            .addFilterBefore(
                authFilter(),
                UsernamePasswordAuthenticationFilter.class
            );
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .userDetailsService(userDetailsService)
            .passwordEncoder(passwordEncoder());
    }

    @Autowired
    private UserDetailsService userDetailsService;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

      

Input filter

public class JwtSignInFilter extends AbstractAuthenticationProcessingFilter {

    @Autowired
    private TokenAuthenticationService tokenAuthService;

    public JwtSignInFilter(RequestMatcher requestMatcher, AuthenticationManager authManager) {
        super(requestMatcher);
        setAuthenticationManager(authManager);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException, IOException, ServletException {
        if (CorsUtils.isPreFlightRequest(req)) {
            res.setStatus(HttpServletResponse.SC_OK);
            return null;
        }

        if (!req.getMethod().equals(HttpMethod.POST.name())) {
            res.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return null;
        }

        SignInRequest creds = new ObjectMapper().readValue(
            req.getInputStream(),
            SignInRequest.class
        );

        return getAuthenticationManager().authenticate(
            new UsernamePasswordAuthenticationToken(
                creds.getEmail(),
                creds.getPassword(),
                emptyList()
            )
        );
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain, Authentication auth) throws IOException, ServletException {
        tokenAuthService.addAuthentication(res, auth.getName());
    }
}

      

+4


source







All Articles