同源策略是一套浏览器的安全机制,它对同源资源放行,对跨域资源限制。
因此限制造成的开发问题,称之为跨域问题。
源 = 协议 + 域名 + 端口
同源即相同的协议、域名和端口号。
本文重点讨论网络通信中的Ajax跨域问题。
跨源资源共享(CORS,或通俗地译为跨域资源共享)是一种基于 HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其他源(域、协议或端口),使得浏览器允许这些源访问加载自己的资源。跨源资源共享还通过一种机制来检查服务器是否会允许要发送的真实请求,该机制通过浏览器发起一个到服务器托管的跨源资源的“预检”请求。在预检中,浏览器发送的头中标示有 HTTP 方法和真实请求中会用到的头。
CORS的基本理念是:
所以,使用CORS解决跨域,必须要保证服务器是「自己人」。
CORS将请求分为两类:简单请求和预检请求
对不同种类的请求它的规则有所区别。
所以要理解CORS,首先要理解它是如何划分请求的。
完整判定逻辑:简单请求
简单来说,只要满足下列条件,就是简单请求:
浏览器请求默认自带的头部字段都是满足安全规范的,只要开发者不改动和新增头部,就不会打破此规则。
不是简单请求的就是预检请求,“需预检的请求”要求必须首先使用 OPTIONS
方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。“预检请求“的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。
OPTIONS 预检请求中同时携带了下面两个标头字段:
标头字段 Access-Control-Request-Method
告知服务器,实际请求将使用 POST
方法。标头字段 Access-Control-Request-Headers
告知服务器,实际请求将携带三个自定义请求标头字段:a、b 与 Content-Type。服务器据此决定,该实际请求是否被允许。
服务器响应预检请求,表明服务器将接受后续的实际请求方法和实际请求头。
服务器的响应携带了 Access-Control-Allow-Origin: http://abc.com
,从而限制请求的源域。同时,携带的 Access-Control-Allow-Methods
表明服务器允许客户端使用 POST
方法发起请求。
标头字段 Access-Control-Allow-Headers
表明服务器允许请求中携带字段 a、b 与 Content-Type。与 Access-Control-Allow-Methods
一样,Access-Control-Allow-Headers
的值为逗号分割的列表。
最后,标头字段 Access-Control-Max-Age
给定了该预检请求可供缓存的时间长短,单位为秒,默认值是 5 秒。在有效时间内,浏览器无须为同一请求再次发起预检请求。以上例子中,该响应的有效时间为 86400 秒,也就是 24 小时。请注意,浏览器自身维护了一个最大有效时间,如果该标头字段的值超过了最大有效时间,将不会生效。
预检请求完成之后,发送实际请求。
默认情况下,ajax的跨域请求并不会附带cookie,某些需要权限的操作就无法进行。
我们可以通过简单的配置来实现跨域请求附带cookie的效果:
这样,无论是简单请求还是预检请求,都会在请求头中添加cookie字段。
但是,如果服务器端的响应中未携带 Access-Control-Allow-Credentials
: true
,浏览器将不会把响应内容返回给请求的发送者。具体来说:
同时,对于附带身份凭证的请求,服务器:
通过在响应头中设置Access-Control-Expose-Headers
。
在跨源访问时,XMLHttpRequest
对象的 getResponseHeader()
方法(或者fetch中使用headers.get())只能拿到一些最基本的响应头,Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,如果要访问其他头,则需要服务器设置该响应头。
使用<script>
标签发起跨域请求,响应回来的参数是一个callbackName({msg: 'xxxxx'})
的形式。
缺陷:
恶意攻击者可能利用callback=恶意函数的方式实现XSS攻击
CORS和JSONP均要求服务器是「自己人」
那如果不是呢?那就找一个「中间人」
最重要的依据:保持生产环境和开发环境一致
选择跨域解决方案决策图:
具体使用场景: