为了改善网络应用程序, 开发人员要求浏览器供应商允许跨域请求. 跨域请求主要用于:
调用 XMLHttpRequest 或 fetchAPI 通过跨站点方式访问资源
网络字体, 例如 Bootstrap(通过 CSS 使用 @font-face 跨域调用字体)
通过 canvas 标签, 绘制图表和视频.
CORS 的安全隐患
跨域请求和 Ajax 技术都会极大地提高页面的体验, 但同时也会带来安全的隐患, 其中最主要的隐患来自于 CSRF(Cross-site request forgery)跨站请求伪造.
CSRF 攻击的大致原理是:
用户通过浏览器, 访问正常网站 A(例如某银行), 通过用户的身份认证 (比如用户名 / 密码) 成功 A 网站.
网站 A 产生 Cookie 信息并返回给用户的浏览器;
用户保持 A 网站页面登录状态, 在同一浏览器中, 打开一个新的 TAB 页访问恶意网站 B;
网站 B 接收到用户请求后, 返回一些攻击性代码, 请求 A 网站的资源(例如转账请求);
浏览器执行恶意代码, 在用户不知情的情况下携带 Cookie 信息, 向网站 A 发出请求.
网站 A 根据用户的 Cookie 信息核实用户身份(此时用户在 A 网站是已登录状态),A 网站会处理该请求, 导致来自网站 B 的恶意请求被执行.
CORS 验证机制
出于安全原因, 浏览器限制从脚本中发起的跨域 HTTP 请求. 默认的安全限制为同源策略, 即 JavaScript 或 Cookie 只能访问同域下的内容.
W3C 推荐了一种跨域的访问验证的机制, 即 CORS(Cross-Origin Resource Sharing 跨源资源共享).
这种机制让 web 应用服务器能支持跨站访问控制, 使跨站数据传输更加安全, 减轻跨域 HTTP 请求的风险.
CORS 验证机制需要客户端和服务端协同处理.
CORS 浏览器支持情况
目前主流浏览器都已基本提供对跨域资源共享的支持, 移动端浏览器也几乎全部支持.
客户端处理机制
基于上述的 CSRF 的风险, 各主流的浏览器都会对动态的跨域请求进行特殊的验证处理. 验证处理分为简单请求验证处理和预先请求验证处理.
简单请求
当请求同时满足下面两个条件时, 浏览器会直接发送 GET 请求, 在同一个请求中做跨域权限的验证.
请求方法是下列之一:
GET
HEAD
POST
请求头中的 Content-Type 请求头的值是下列之一:
application/x-www-form-urlencoded
multipart/form-data
text/plain
简单请求时, 浏览器会直接发送跨域请求, 并在请求头中携带 Origin 的 header, 表明这是一个跨域的请求.
服务器端接到请求后, 会根据自己的跨域规则, 通过 Access-Control-Allow-Origin 和 Access-Control-Allow-Methods 响应头, 来返回验证结果.
如果验证成功, 则会直接返回访问的资源内容.
如果验证失败, 则返回 403 的状态码, 不会返回跨域请求的资源内容.
可以通过浏览器的 Console 查看具体的验证失败原因
预先请求
当请求满足下面任意一个条件时, 浏览器会先发送一个 OPTION 请求, 用来与目标域名服务器协商决定是否可以发送实际的跨域请求.
请求方法不是下列之一:
GET
HEAD
POST
请求头中的 Content-Type 请求头的值不是下列之一:
application/x-www-form-urlencoded
multipart/form-data
text/plain
浏览器在发现页面中有上述条件的动态跨域请求的时候, 并不会立即执行对应的请求代码, 而是会先发送 Preflighted requests(预先验证请求),Preflighted requests 是一个 OPTION 请求, 用于询问要被跨域访问的服务器, 是否允许当前域名下的页面发送跨域的请求.
OPTIONS 请求头部中会包含以下头部: Origin,Access-Control-Request-Method,Access-Control-Request-Headers.
服务器收到 OPTIONS 请求后, 设置 Access-Control-Allow-Origin,Access-Control-Allow-Method,Access-Control-Allow-Headers 头部与浏览器沟通来判断是否允许这个请求.
如果 Preflighted requests 验证通过, 浏览器才会发送真正的跨域请求.
如果 Preflighted requests 验证失败, 则会返回 403 状态, 浏览器不会发送真正的跨域请求.
可以通过浏览器的 Console 查看具体的验证失败原因
带认证的请求
默认情况下, 跨源请求不提供凭据 (cookie,HTTP 认证及客户端 SSL 证明等). 通过将 withCredentials 属性设置为 true, 可以指定某个请求应该发送凭据.
xhr.withCredentials = true;
如果服务器接收带凭据的请求, 会用下面的 HTTP 头部来响应.
Access-Control-Allow-Credentials: true
服务器还可以在 Preflight 响应中发送这个 HTTP 头部, 表示允许源发送带凭据的请求.
如果发送的是带凭据的请求, 但服务器的响应中没有包含这个头, 那么浏览器就不会把响应交给 JavaScript(responseText 中将是空字符串, size 为 0).
注意, 当 withCredentials 属性设置为 true, 需要 response header 中的'Access-Control-Allow-Origin'为一个确定的域名, 而不能使用'*'这样的通配符.
服务端处理机制
服务器端对于跨域请求的处理流程如下:
首先查看 http 头部有无 origin 字段;
如果没有, 或者不允许, 直接当成普通请求处理, 结束;
如果有并且是允许的, 那么再看是否是 preflight(method=OPTIONS);
如果不是 preflight(简单请求), 就返回 Allow-Origin,Allow-Credentials 等, 并返回正常内容.
如果是 preflight(预先请求), 就返回 Allow-Headers,Allow-Methods 等, 内容为空;
HTTP Header
Request header
Origin
Origin 头在跨域请求或预先请求中, 标明发起跨域请求的源域名.
Access-Control-Request-Method
Access-Control-Request-Method 头用于表明跨域请求使用的实际 HTTP 方法
Access-Control-Request-Headers
Access-Control-Request-Headers 用于在预先请求时, 告知服务器要发起的跨域请求中会携带的请求头信息
Response header
Access-Control-Allow-Origin
Access-Control-Allow-Origin 头中携带了服务器端验证后的允许的跨域请求域名, 可以是一个具体的域名或是一个 *(表示任意域名). 简单请求时, 浏览器会根据此响应头的内容决定是否给脚本返回相应内容, 预先验证请求时, 浏览器会根据此响应头决定是否发送实际的跨域请求.
Access-Control-Expose-Headers
Access-Control-Expose-Headers 头用于允许返回给跨域请求的响应头列表, 在列表中的响应头的内容, 才可以被浏览器访问.
Access-Control-Max-Age
Access-Control-Max-Age 用于告知浏览器可以将预先检查请求返回结果缓存的时间, 在缓存有效期内, 浏览器会使用缓存的预先检查结果判断是否发送跨域请求.
Access-Control-Allow-Credentials
Access-Control-Allow-Credentials 用于告知浏览器当 withCredentials 属性设置为 true 时, 是否可以显示跨域请求返回的内容. 简单请求时, 浏览器会根据此响应头决定是否显示响应的内容. 预先验证请求时, 浏览器会根据此响应头决定在发送实际跨域请求时, 是否携带认证信息.
Access-Control-Allow-Methods
Access-Control-Allow-Methods 用于告知浏览器可以在实际发送跨域请求时, 可以支持的请求方法, 可以是一个具体的方法列表或是一个 *(表示任意方法). 简单请求时, 浏览器会根据此响应头的内容决定是否给脚本返回相应内容, 预先验证请求时, 浏览器会根据此响应头决定是否发送实际的跨域请求.
Access-Control-Allow-Headers
Access-Control-Allow-Headers 用于告知浏览器可以在实际发送跨域请求时, 可以支持的请求头, 可以是一个具体的请求头列表或是一个 *(表示任意请求头). 简单请求时, 浏览器会根据此响应头的内容决定是否给脚本返回相应内容, 预先验证请求时, 浏览器会根据此响应头决定是否发送实际的跨域请求.
配置 CORS 规则
nginx 上的 CORS 配置
OSS 上的 CORS 配置
CDN 上的 CORS 配置
注意: 由于 CDN 的缓存特性, CDN 配合 OSS 时, 需要在 CDN 中设置 CORS 配置.
来源: http://blog.csdn.net/gaowenhui2008/article/details/79231380