Skip to the content.

[TOC]

CORS

一、是什么?

​ 全称是”跨域资源共享”(Cross-origin resource sharing)。

​ A、引入概念

​ 1、简单请求: ​ 1): 请求方式只能是:headgetpost ​ 2): 请求头允许的字段:AcceptAccept-LanguageContent-LanguageLast-Event-IDContent-Type:application/x-www-form-urlencoded、multipart/form-data、text/plain 三选一

​ 2、复杂请求:不满足上面的的情况

二、为什么?

​ 浏览器的同源安全策略:浏览器只允许请求当前域的资源,而对其他域的资源表示不信任。那怎么才算跨域呢?

​ 1、请求协议http,https的不同

​ 2、域domain的不同

​ 3、端口port的不同

三、怎么避免

A、简单请求

​ 浏览器:往请求头添加origin,请求头origin字段为当前域

​ 服务器:查看请求头的origin,符合要求,放行,并在响应头中放入服务器的规则。

cors_req

​ 其中,最重要的就是Access-Control-Allow-Origin,标识允许哪个域的请求。当然,如果服务器不通过,根本没有这个字段,接着触发XHRonerror,再接着你就看到浏览器的提示xxx的服务器没有响应Access-Control-Allow-Origin字段

​ 上面第一行说到的Access-Control-Allow-Origin有多种设置方法:

  1. 设置*是最简单粗暴的,但是服务器出于安全考虑,肯定不会这么干,而且,如果是*的话,浏览器将不会发送cookies,即使你的XHR设置了withCredentials

    (PS:withCredentials:表示XHR是否接收cookies和发送cookies,也就是说如果该值是false,响应头的Set-Cookie,浏览器也不会理,并且即使有目标站点的cookies,浏览器也不会发送。)

  2. 指定域,如上图中的http://www.demo.cn

  3. 动态设置为请求域,多人协作时,多个前端对接一个后台,这样很方便

B、复杂请求:

​ 最常见的情况,当我们使用putdelete请求时,浏览器会先发送option(预检)请求。

​ 预检请求:与简单请求不同的是,option请求多了2个字段:

Access-Control-Request-Method:该次请求的请求方式 Access-Control-Request-Headers:该次请求的自定义请求头字段

​ 服务器检查通过后,做出响应:

​ // 指定允许其他域名访问 ​ Access-Control-Allow-Origin:http://www.demo.cn ​ // 是否允许后续请求携带认证信息(cookies),该值只能是true,否则不返回 ​ Access-Control-Allow-Credentials:true ​ // 预检结果缓存时间,也就是上面说到的缓存啦 ​ Access-Control-Max-Age: 1800 ​ // 允许的请求类型 ​ Access-Control-Allow-Methods:GET,POST,PUT,POST ​ // 允许的请求头字段 ​ Access-Control-Allow-Headers:x-requested-with,content-type

​ 这里有个注意点:Access-Control-Request-MethodAccess-Control-Request-Headers返回的是满足服务器要求的所有请求方式,请求头,不限于该次请求。

SpringMVC&CORS

CORS 官方文档:https://spring.io/blog/2015/06/08/cors-support-in-spring-framework

一、源码分析

Web应用启动时候,注册Handler(即Controller),会识别method上是否有对应的注解CorsConfig,如果有则注册到 Map<HandlerMethod, CorsConfiguration> corsLookup 中,具体维护方法:

当外部请求请求Web应用时,请求经过一系列的处理,最终进入SpringMVCorg.springframework.web.servlet.DispatcherServlet#doDispatch进行处理,此方法会调用org.springframework.web.servlet.DispatcherServlet#getHandler 拿出和请求相匹配的对应的HandlerExecutionChain,拿出HandlerExecutionChain后,调用org.springframework.web.servlet.HandlerExecutionChain#applyPreHandle ,方法内会获取所有HandlerInterceptor,并进行HandlerInterceptor.preHandle,当有一个拦截器没有校验通过时,则触发所有HandlerInterceptor.afterCompletion后,返回false,否则返回true

其中就包含 org.springframework.web.servlet.handler.AbstractHandlerMapping.CorsInterceptor,实际调用时序,如下图。

    title: request到Handler
    participant ...
    participant DispatcherServlet as DS
    participant HandlerExecutionChain as HEC
    participant HandlerInterceptor as HI
    
    ...->DS:DispatcherServlet.doService
    DS->DS:doDispatch
    DS->DS:getHandler
    DS->HEC:HandlerExecutionChain.applyPreHandle
    HEC->HEC:getInterceptors
    HEC->HI:HandlerInterceptor.preHandle
    HEC->HI:IF preHandle = FALSE THEN triggerAfterCompletion HandlerInterceptor.afterCompletion
    
    HEC-->DS:Yes Or No?
    DS-->DS: Yes: do ModelAndView,No just return
	

下图是为了展示org.springframework.web.servlet.handler.AbstractHandlerMapping.CorsInterceptor的细节:

title: HandlerInterceptor.preHandle细节
participant CorsInterceptor as CI
participant CorsProcessor as CP

note left of CI:CorsInterceptor是HandlerInterceptor的一个实现
CI->CP:CorsProcessor.processRequest