week 6 CORS
1. CORS 简介
同源策略( same origin policy )是浏览器安全的基石。在同源策略的限制下,非同源的网站之间不能发送 ajax 请求的。
为了解决这个问题, w3c 提出了跨源资源共享,即 CORS(Cross-Origin Resource Sharing)。
CORS 做到了两点:
- 不破坏即有规则
- 服务器实现了 CORS 接口,就可以跨源通信
基于这两点, CORS 将请求分为两类:简单请求和非简单请求。
1.1 简单请求
可以先看下 CORS 出现前的情况:跨源时能够通过 script 或者 image 标签触发 GET 请求或通过表单发送一条 POST 请求,但这两种请求 HTTP 头信息中都不能包含任何自定义字段。
简单请求对应该规则,因此对简单请求的定义为:
请求方法是 HEAD
、GET
或 POST
且 HTTP 头信息不超过以下几个字段:Accept
、Accept-Language
、Content-Language
、Last-Event-ID
、Content-Type
(只限于 application/x-www-form-urlencoded
、multipart/form-data
、text/plain
)。
比如有一个简单请求:
GET /test HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate, sdch, br
Origin: http://www.examples.com
Host: www.examples.com
对于这样的简单请求, CORS 的策略是请求时,在头信息中添加一个 Origin 字段,服务器收到请求后,根据该字段判断是否允许该请求。
- 如果允许,则在 HTTP 头信息中添加
Access-Control-Allow-Origin
字段,并返回正确的结果 - 如果不允许,则不在头信息中添加
Access-Control-Allow-Origin
字段。
浏览器先于用户得到返回结果,根据有无 Access-Control-Allow-Origin
字段来决定是否拦截该返回结果。
对于 CORS 出现前的一些服务, CORS 对他们的影响分两种情况:
- script 或者 image 触发的 GET 请求不包含 Origin 头,所以不受到 CORS 的限制,依旧可用。
- 如果是 ajax 请求, HTTP 头信息中会包含 Origin 字段,由于服务器没有做任何配置,所以返回结果不会包含
Access-Control-Allow-Origin
,因此返回结果会被浏览器拦截,接口依旧不可以被 ajax 跨源访问。
可以看出, CORS 的出现,没有对”旧的“服务造成任何影响。
另外,除了提到的 Access-Control-Allow-Origin
还有几个字段用于描述 CORS 返回结果:
- Access-Control-Allow-Credentials: 可选,用户是否可以发送、处理 cookie 。
- 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 相关的字段有:
Access-Control-Request-Method
: 真实请求使用的 HTTP 方法。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
他们的含义分别是:
- Access-Control-Allow-Methods: 真实请求允许的方法
- Access-Control-Allow-Headers: 服务器允许使用的字段
- Access-Control-Allow-Credentials: 是否允许用户发送、处理 cookie
- Access-Control-Max-Age: 预检请求的有效期,单位为秒。有效期内,不会重复发送预检请求
当预检请求通过后,浏览器会发送真实请求到服务器。这就实现了跨源请求
服务端配置CORS
-
通过WebMvcConfigurerAdapter#addCorsMappings去配置
public class WebConfig extends WebMvcConfigurerAdapter { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedHeaders("*") .allowedMethods("*") .allowedOrigins("*"); } }
-
通过自定义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和端口时,请求正常。
解决过程
- CORS同源配置。如上配置CORS,但请求依然。
- 打开浏览器debug,发现OPTIONS请求直接返回401,授权失败。此时如果先前已经了解CORS就不会产生疑问,CORS会将任何请求先切分为一个OPTIONS和一个原来的。
- 上一步表面OPTIONS请求被授权服务拦截,那么解决问题的方式就出来了。
解决方案
-
方案一,配置Spring security策略,不拦截OPTIONS请求
HttpSecurity#authorizeRequests().antMatchers(HttpMethod.OPTIONS).permitAll()
-
方案二,自定义CorsFilter,设置order为最高优先级或者其他,只需要优先级比Spring security的order高便好。
好的博文推荐: