使用登录过滤器而不是控制器时处理OPTIONS和CORS

men*_*e23 5 spring spring-security cors jwt

我有一个AbstractAuthenticationProcessingFilter我用来处理路径上的POST请求/sign-in.CORS预检请求返回404,因为没有匹配的路径.这对我来说很有意义.

我想知道的是,如果有一种方法可以通知Spring有一个过滤器处理POST(而不是一个控制器),那么Spring可以像控制器处理POST一样调度OPTIONS .用一个控制器写一个控制器是不好的做法PostMapping?我不确定这样做会怎么样,因为技术上过滤器处理POST.

谢谢你的帮助!

更新

这是我的设置.我最初是从手机发布的,因此无法添加这些详细信息.见下文.重申一下,没有控制器/sign-in.POST由JwtSignInFilter.处理.

CORS配置

@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);
    }
}
Run Code Online (Sandbox Code Playgroud)

安全配置

@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
            );
    }
}
Run Code Online (Sandbox Code Playgroud)

过滤

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());
    }
}
Run Code Online (Sandbox Code Playgroud)

验证过滤器

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

men*_*e23 6

好吧,终于找到了解决这个问题的方法.经过几个小时的修补和搜索,我发现我需要使用基于过滤器的CORS配置,然后在登录过滤器中处理CORS预检(OPTIONS请求),只需返回200 OK即可.然后CORS过滤器将添加适当的标头.

下面更新了配置(注意我CorsConfig不再需要了,因为我们有一个CORS过滤器SecurityConfig,并且JwtAuthenticationFilter与之前相同).

安全配置

@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();
    }
}
Run Code Online (Sandbox Code Playgroud)

过滤

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());
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 就我而言,`.cors().and()` 就是答案。 (2认同)