week 6 CORS

1. CORS 简介

同源策略( same origin policy )是浏览器安全的基石。在同源策略的限制下,非同源的网站之间不能发送 ajax 请求的。

为了解决这个问题, w3c 提出了跨源资源共享,即 CORS(Cross-Origin Resource Sharing)。

CORS 做到了两点:

  1. 不破坏即有规则
  2. 服务器实现了 CORS 接口,就可以跨源通信

基于这两点, CORS 将请求分为两类:简单请求和非简单请求。

1.1 简单请求

可以先看下 CORS 出现前的情况:跨源时能够通过 script 或者 image 标签触发 GET 请求或通过表单发送一条 POST 请求,但这两种请求 HTTP 头信息中都不能包含任何自定义字段。

简单请求对应该规则,因此对简单请求的定义为:

请求方法是 HEADGET 或 POST 且 HTTP 头信息不超过以下几个字段:AcceptAccept-LanguageContent-LanguageLast-Event-IDContent-Type(只限于 application/x-www-form-urlencodedmultipart/form-datatext/plain)。

比如有一个简单请求:

GET /test HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate, sdch, br
Origin: http://www.examples.com
Host: www.examples.com

对于这样的简单请求, CORS 的策略是请求时,在头信息中添加一个 Origin 字段,服务器收到请求后,根据该字段判断是否允许该请求。

  1. 如果允许,则在 HTTP 头信息中添加 Access-Control-Allow-Origin 字段,并返回正确的结果
  2. 如果不允许,则不在头信息中添加 Access-Control-Allow-Origin 字段。

浏览器先于用户得到返回结果,根据有无 Access-Control-Allow-Origin 字段来决定是否拦截该返回结果。

对于 CORS 出现前的一些服务, CORS 对他们的影响分两种情况:

  1. script 或者 image 触发的 GET 请求不包含 Origin 头,所以不受到 CORS 的限制,依旧可用。
  2. 如果是 ajax 请求, HTTP 头信息中会包含 Origin 字段,由于服务器没有做任何配置,所以返回结果不会包含 Access-Control-Allow-Origin,因此返回结果会被浏览器拦截,接口依旧不可以被 ajax 跨源访问。

可以看出, CORS 的出现,没有对”旧的“服务造成任何影响。

另外,除了提到的 Access-Control-Allow-Origin 还有几个字段用于描述 CORS 返回结果:

  1. Access-Control-Allow-Credentials: 可选,用户是否可以发送、处理 cookie 。
  2. Access-Control-Expose-Headers :可选,可以让用户拿到的字段。有几个字段无论设置与否都可以拿到的,包括: Cache-Control 、 Content-Language 、 Content-Type 、 Expires 、 Last-Modified 、 Pragma 。

1.2 非简单请求

除了简单请求之外的请求,就是非简单请求。

对于非简单请求的跨源请求,浏览器会在真实请求发出前,增加一次 OPTION 请求,称为预检请求( preflight request )。预检请求将真实请求的信息,包括请求方法、自定义头字段、源信息添加到 HTTP 头信息字段中,询问服务器是否允许这样的操作。

比如对于 DELETE 请求:

OPTIONS /test HTTP/1.1
Origin: http://www.examples.com
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: X-Custom-Header
Host: www.examples.com

与 CORS 相关的字段有:

  1. Access-Control-Request-Method: 真实请求使用的 HTTP 方法。
  2. Access-Control-Request-Headers: 真实请求中包含的自定义头字段。

服务器收到请求时,需要分别对 Origin 、 Access-Control-Request-Method 、 Access-Control-Request-Headers 进行验证,验证通过后,会在返回 Http 头信息中添加

Access-Control-Allow-Origin: http://www.examples.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000

他们的含义分别是:

  1. Access-Control-Allow-Methods: 真实请求允许的方法
  2. Access-Control-Allow-Headers: 服务器允许使用的字段
  3. Access-Control-Allow-Credentials: 是否允许用户发送、处理 cookie
  4. Access-Control-Max-Age: 预检请求的有效期,单位为秒。有效期内,不会重复发送预检请求

当预检请求通过后,浏览器会发送真实请求到服务器。这就实现了跨源请求

服务端配置CORS

  1. 通过WebMvcConfigurerAdapter#addCorsMappings去配置

    public class WebConfig extends WebMvcConfigurerAdapter {
         @Override
         public void addCorsMappings(CorsRegistry registry) {
             registry.addMapping("/**")
                     .allowedHeaders("*")
                     .allowedMethods("*")
                     .allowedOrigins("*");
         }
     }
  2. 通过自定义Filter

     @Bean
     public FilterRegistrationBean corsFilter() {
         UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
         CorsConfiguration config = new CorsConfiguration();
         config.addAllowedOrigin("*");
         config.setAllowCredentials(true);
         config.addAllowedHeader("*");
         config.addAllowedMethod("*");
         source.registerCorsConfiguration("/**", config);
    
         FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
         bean.setOrder(0);
         return bean;
     }

这两种方式实现的机制完全不一样,也就是产生作用的生命周期不一样。

  • 方式一,WebMvcConfigurerAdapter的配置最终将会转换为RequestMappingHandlerMapping,而这个Handler最终是被DispatcherServlet调用,也就是说,方式一的配置将会在Servlet中被调用。
  • 方式二,很容易理解,配置的Filter将会被springboot自动配置到Tomcat或其他web容器中。ServletContextInitializerBeans#addAdaptableBeans方法中,将自动查找spring容器中存在的Filter实现,并且根据@Order或Order来判断Filter的排序。

冲突

keycloak场景

前端已经通过javascript的接口,从keycloak验证并获得了token,keycloak作为单点登录系统,理论上是可以通过token登录并验证任何一个后端服务,但是,当按照文档上面的描述,正确配置springboot及security,后端依然无法通过token的验证。但当将前后端使用同一个IP和端口时,请求正常。

解决过程

  1. CORS同源配置。如上配置CORS,但请求依然。
  2. 打开浏览器debug,发现OPTIONS请求直接返回401,授权失败。此时如果先前已经了解CORS就不会产生疑问,CORS会将任何请求先切分为一个OPTIONS和一个原来的。
  3. 上一步表面OPTIONS请求被授权服务拦截,那么解决问题的方式就出来了。

解决方案

  • 方案一,配置Spring security策略,不拦截OPTIONS请求

      HttpSecurity#authorizeRequests().antMatchers(HttpMethod.OPTIONS).permitAll()
    
  • 方案二,自定义CorsFilter,设置order为最高优先级或者其他,只需要优先级比Spring security的order高便好。

参考链接:https://www.jianshu.com/p/27060722843b

好的博文推荐:

spring Cors拦截流程

原文地址:https://www.cnblogs.com/EST-woah/p/10666560.html