[CORS][Spring Security] PreFlight 请求未处理

Posted

技术标签:

【中文标题】[CORS][Spring Security] PreFlight 请求未处理【英文标题】:[CORS][SpringSecurity] PreFlight request not handle 【发布时间】:2020-06-30 20:09:02 【问题描述】:

前端:React 16.12.0 |后端:Spring 2.2.4.RELEASE

我目前面临关于预检 CORS 请求的问题。据我了解,每个非简单请求(例如,带有授权令牌的 GET)都会触发一个预检 CORS 请求,服务器必须通过包含所有允许参数的响应来验证该请求之后发送的“真实”请求应该尊重。

预检作为 OPTION 请求进入服务器端,应该点击我的 cors 过滤器以进行验证。

请求从前端发送:

export function greeting() 
    const access_token = AuthTokenFromStore().oauthData.access_token;
    const obj =   
        method: 'GET',
        headers: 

        'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
        'Accept': 'application/json',
            'Authorization': 'Bearer ' + access_token
        
    ;
    return (fetch('http://localhost:8080/api/v1/greeting', obj)
    .then(res => res.json()));

错误信息接收:

OPTIONS http://localhost:8080/api/v1/greeting 401
Access to fetch at 'http://localhost:8080/api/v1/greeting' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

弹簧安全配置:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityServerConfiguration extends WebSecurityConfigurerAdapter 
    @Override
    protected void configure(final HttpSecurity http) throws Exception 
        http
                .cors() // <-- fetch the corsFilter bean
                .and()
                .csrf().disable()
                .antMatcher("/**")
                .authorizeRequests()
                .antMatchers("/oauth/authorize**", "/login**", "/error**")
                .permitAll()
                .and()
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin().permitAll();
    
//...

cors 过滤器配置:

@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CORSConfig 
    @Bean
    public CorsFilter corsFilter() 
        final CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000"));
        configuration.setAllowCredentials(true);
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
        configuration.setAllowedHeaders(Arrays.asList("origin", "x-authorization", "content-type", "accept"));
        configuration.setMaxAge(3600L);
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return new CorsFilter(source);
    

我的日志:

2020-03-19 17:00:24.993 DEBUG 9452 --- [nio-8080-exec-2] o.a.coyote.http11.Http11InputBuffer      : Received [OPTIONS /api/v1/greeting HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Access-Control-Request-Method: GET
Origin: http://localhost:3000
Sec-Fetch-Dest: empty
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (Khtml, like Gecko) Chrome/80.0.3987.132 Safari/537.36
Access-Control-Request-Headers: authorization
Accept: */*
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: cors
Referer: http://localhost:3000/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8

]

2020-03-19 17:00:24.994 DEBUG 9452 --- [nio-8080-exec-2] o.a.c.authenticator.AuthenticatorBase    : Security checking request OPTIONS /api/v1/greeting
2020-03-19 17:00:24.994 DEBUG 9452 --- [nio-8080-exec-2] org.apache.catalina.realm.RealmBase      :   No applicable constraints defined
2020-03-19 17:00:24.994 DEBUG 9452 --- [nio-8080-exec-2] o.a.c.authenticator.AuthenticatorBase    : Not subject to any constraint
2020-03-19 17:00:25.000 DEBUG 9452 --- [nio-8080-exec-2] o.s.s.web.util.matcher.OrRequestMatcher  : Trying to match using Ant [pattern='/oauth/token']
2020-03-19 17:00:25.000 DEBUG 9452 --- [nio-8080-exec-2] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/api/v1/greeting'; against '/oauth/token'
2020-03-19 17:00:25.000 DEBUG 9452 --- [nio-8080-exec-2] o.s.s.web.util.matcher.OrRequestMatcher  : Trying to match using Ant [pattern='/oauth/token_key']
2020-03-19 17:00:25.000 DEBUG 9452 --- [nio-8080-exec-2] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/api/v1/greeting'; against '/oauth/token_key'
2020-03-19 17:00:25.000 DEBUG 9452 --- [nio-8080-exec-2] o.s.s.web.util.matcher.OrRequestMatcher  : Trying to match using Ant [pattern='/oauth/check_token']
2020-03-19 17:00:25.000 DEBUG 9452 --- [nio-8080-exec-2] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/api/v1/greeting'; against '/oauth/check_token'
2020-03-19 17:00:25.000 DEBUG 9452 --- [nio-8080-exec-2] o.s.s.web.util.matcher.OrRequestMatcher  : No matches found
2020-03-19 17:00:25.000 DEBUG 9452 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy        : /api/v1/greeting at position 1 of 10 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2020-03-19 17:00:25.000 DEBUG 9452 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy        : /api/v1/greeting at position 2 of 10 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2020-03-19 17:00:25.000 DEBUG 9452 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy        : /api/v1/greeting at position 3 of 10 in additional filter chain; firing Filter: 'HeaderWriterFilter'
2020-03-19 17:00:25.000 DEBUG 9452 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy        : /api/v1/greeting at position 4 of 10 in additional filter chain; firing Filter: 'LogoutFilter'
2020-03-19 17:00:25.000 DEBUG 9452 --- [nio-8080-exec-2] o.s.s.web.util.matcher.OrRequestMatcher  : Trying to match using Ant [pattern='/logout', GET]
2020-03-19 17:00:25.000 DEBUG 9452 --- [nio-8080-exec-2] o.s.s.w.u.matcher.AntPathRequestMatcher  : Request 'OPTIONS /api/v1/greeting' doesn't match 'GET /logout'
2020-03-19 17:00:25.000 DEBUG 9452 --- [nio-8080-exec-2] o.s.s.web.util.matcher.OrRequestMatcher  : Trying to match using Ant [pattern='/logout', POST]
2020-03-19 17:00:25.000 DEBUG 9452 --- [nio-8080-exec-2] o.s.s.w.u.matcher.AntPathRequestMatcher  : Request 'OPTIONS /api/v1/greeting' doesn't match 'POST /logout'
2020-03-19 17:00:25.000 DEBUG 9452 --- [nio-8080-exec-2] o.s.s.web.util.matcher.OrRequestMatcher  : Trying to match using Ant [pattern='/logout', PUT]
2020-03-19 17:00:25.000 DEBUG 9452 --- [nio-8080-exec-2] o.s.s.w.u.matcher.AntPathRequestMatcher  : Request 'OPTIONS /api/v1/greeting' doesn't match 'PUT /logout'
2020-03-19 17:00:25.000 DEBUG 9452 --- [nio-8080-exec-2] o.s.s.web.util.matcher.OrRequestMatcher  : Trying to match using Ant [pattern='/logout', DELETE]
2020-03-19 17:00:25.000 DEBUG 9452 --- [nio-8080-exec-2] o.s.s.w.u.matcher.AntPathRequestMatcher  : Request 'OPTIONS /api/v1/greeting' doesn't match 'DELETE /logout'
2020-03-19 17:00:25.000 DEBUG 9452 --- [nio-8080-exec-2] o.s.s.web.util.matcher.OrRequestMatcher  : No matches found
2020-03-19 17:00:25.000 DEBUG 9452 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy        : /api/v1/greeting at position 5 of 10 in additional filter chain; firing Filter: 'OAuth2AuthenticationProcessingFilter'
2020-03-19 17:00:25.001 DEBUG 9452 --- [nio-8080-exec-2] o.s.s.o.p.a.BearerTokenExtractor         : Token not found in headers. Trying request parameters.
2020-03-19 17:00:25.006 DEBUG 9452 --- [nio-8080-exec-2] org.apache.tomcat.util.http.Parameters   : Set encoding to UTF-8
2020-03-19 17:00:25.006 DEBUG 9452 --- [nio-8080-exec-2] o.s.s.o.p.a.BearerTokenExtractor         : Token not found in request parameters.  Not an OAuth2 request.
2020-03-19 17:00:25.006 DEBUG 9452 --- [nio-8080-exec-2] p.a.OAuth2AuthenticationProcessingFilter : No token in request, will continue chain.
2020-03-19 17:00:25.006 DEBUG 9452 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy        : /api/v1/greeting at position 6 of 10 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
2020-03-19 17:00:25.006 DEBUG 9452 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy        : /api/v1/greeting at position 7 of 10 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
2020-03-19 17:00:25.006 DEBUG 9452 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy        : /api/v1/greeting at position 8 of 10 in additional filter chain; firing Filter: 'SessionManagementFilter'
2020-03-19 17:00:25.006 DEBUG 9452 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy        : /api/v1/greeting at position 9 of 10 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
2020-03-19 17:00:25.006 DEBUG 9452 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy        : /api/v1/greeting at position 10 of 10 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
2020-03-19 17:00:25.006 DEBUG 9452 --- [nio-8080-exec-2] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/api/v1/greeting'; against '/api/**'
2020-03-19 17:00:25.006 DEBUG 9452 --- [nio-8080-exec-2] o.s.s.w.a.i.FilterSecurityInterceptor    : Secure object: FilterInvocation: URL: /api/v1/greeting; Attributes: [#oauth2.throwOnError(authenticated)]
2020-03-19 17:00:25.010 DEBUG 9452 --- [nio-8080-exec-2] o.s.s.w.a.ExceptionTranslationFilter     : Authentication exception occurred; redirecting to authentication entry point

org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext

在调试期间,我可以看到 http.cors() 正确获取了我的 bean corsFilter 但为什么它没有出现在日志中?

我测试了很多解决方案(google reasearcg、tutorials、official spring docs、baeldung、***...等),没有人适合我。此外,该框架为我们提供了一种正确执行此操作的方法,因此我想避免手动添加过滤器来绕过 OPTIONS 请求(这将是我最后的手段)。

有人可以帮我解决这个问题吗?或者指出我做错了什么?

感谢您的帮助!

【问题讨论】:

这个日志输出其实和cors关系不大。它实际上告诉您只允许经过 oauth2 身份验证的用户执行预检请求。您没有在请求中发送任何令牌,并且您的会话中没有身份验证对象。因此,您不得执行此类操作。 日志显示过滤器未正确处理请求(预检),我将编辑我的问题以添加更多详细信息。 能否添加 DefaultSecurityFilterChain 的日志输出以查看您实际使用的过滤器以及使用的顺序? 【参考方案1】:

找出问题所在,我已经同时配置了 ResourceServerConfigurerAdapter 和我的 WebSecurityConfigurerAdapter。似乎 ResourceServerConfigurerAdapter 具有更高的优先级,因此我的所有自定义配置都没有考虑在内。 WebSecurityConfigurerAdapter 的唯一目的是为 OAuth2 提供身份验证过程。 所以我在我的安全配置中将配置方法简化到最低限度,并将所有其他配置移动到 resourceServer 配置,尤其是 cors 过滤器,它是我原来的阻塞点的解决方案。

讨论: ResourceServerConfigurerAdapter vs WebSecurityConfigurerAdapter

【讨论】:

以上是关于[CORS][Spring Security] PreFlight 请求未处理的主要内容,如果未能解决你的问题,请参考以下文章

Spring-Security:使用 CORS Preflights 获得 401(尽管 http.cors())

Spring-security 的 CORS 问题

Spring Security CORS:来源已被 CORS 策略阻止

CORS与CSRF在Spring Security中的使用

Spring Security Logout 不适用于 Spring 4 CORS

Spring 4 Security + CORS + AngularJS - 奇怪的重定向