防跨域于我而言一直是个模糊的怪兽。每次遇到的时候都只会去找百度超人帮忙,却从没尝试要自己解决问题。直到昨天,Hokori学长把我从舒适圈踢了出去,我这才第一次直面这个怪兽,解开它的真面目。
前置工作
在了解跨域问题之前,首先需要了解浏览器的同源策略。
同源策略是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。
浏览器是我们接触互联网的一个主要工具之一。在面对互联网琳琅满目的信息时,我们也需要注意其中的危险,例如诈骗信息,病毒等等。而浏览器为了我们的安全着想,就整出了这么个同源策略。通过这个同源策略,我们可以轻松规避一些问题,例如
- 防止JavaScript代码对非同源页面的各种请求(CSRF攻击)
- 限制对其他页面DOM元素的读取(读取你的用户名密码)
跨域问题的出现
跨域问题你可能也已经猜到了,就是触发了浏览器的同源策略的干预。在采用前后端分离的时候,我们并不会将两个部分放在同一个服务器上,而且域名及端口一般也不同。而同源的定义恰好就是同域名,同端口,同协议。举个例子,与http://store.company.com/dir/page.html
的源进行对比,不同URL的不同结果如下表[tip]数据来自MDN[/tip]
URL | 结果 | 原因 |
---|---|---|
http://store.company.com/dir2/other.html | 同源 | 只有路径不同 |
http://store.company.com/dir/inner/another.html | 同源 | 只有路径不同 |
https://store.company.com/secure.html | 非同源 | 协议不同 |
http://store.company.com:81/dir/etc.html | 非同源 | 端口不同 |
http://news.company.com/dir/other.html | 非同源 | 子域不同 |
这样一看,进行前后端分离时,大概率会触发同源策略的干预了。那么怎么解决呢?
解决方案
现在主流的解决方案基本上都是基于CORS(跨源资源共享)。
在探讨CORS之前,要得先了解一下HTTP中的简单请求与非简单请求。简单请求包含GET
, HEAD
, POST
,同时它们的HTTP头信息不得多于以下信息
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type(
application/x-www-form-urlencoded
、multipart/form-data
、text/plain
)
不满足以上条件的则是复杂请求。
那么了解了简单请求之后,再来看看CORS。
跨源资源共享 (CORS) (或通俗地译为跨域资源共享)是一种基于HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其它origin(域,协议和端口),这样浏览器可以访问加载这些资源。跨源资源共享还通过一种机制来检查服务器是否会允许要发送的真实请求,该机制通过浏览器发起一个到服务器托管的跨源资源的"预检"请求。在预检中,浏览器发送的头中标示有HTTP方法和真实请求中会用到的头。
如MDN所言,CORS在发送真实请求之前会通过一个"预检"请求来判断是否能够继续发送。具体来说就是当发送复杂请求之前,会先发送一个 OPTIONS
请求来判断。当服务器返回了允许的信号后,浏览器才会继续发送真实请求。“预检请求”的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。
而简单请求则不需要进行“预检请求”。
发送预检请求时一般会带有以下头部信息
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
首部字段 Access-Control-Request-Method
告知服务器,实际请求将使用 POST 方法。首部字段 Access-Control-Request-Headers
告知服务器,实际请求将携带两个自定义请求首部字段:X-PINGOTHER
与 Content-Type
。服务器据此决定,该实际请求是否被允许。
而服务器的预检响应一般带有
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
首部字段
Access-Control-Allow-Methods
表明服务器允许客户端使用POST,
GET
和OPTIONS
方法发起请求。该字段与 HTTP/1.1 Allow: response header 类似,但仅限于在需要访问控制的场景中使用。首部字段
Access-Control-Allow-Headers
表明服务器允许请求中携带字段X-PINGOTHER
与Content-Type
。与Access-Control-Allow-Methods
一样,Access-Control-Allow-Headers
的值为逗号分割的列表。最后,首部字段
Access-Control-Max-Age
表明该响应的有效时间为 86400 秒,也就是 24 小时。在有效时间内,浏览器无须为同一请求再次发起预检请求。请注意,浏览器自身维护了一个最大有效时间,如果该首部字段的值超过了最大有效时间,将不会生效。
说到这里,其实我们的解决方案也差不多出来了。
1. 通过WEB服务器配置
以nginx
为例,只需要配置文件中添加
location / {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
if ($request_method = 'OPTIONS') {
return 204;
}
}
基本上就可以万事大吉。
2. 在后端添加响应头
以PHP
为例,在入口文件添加
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
header('Access-Control-Allow-Headers: DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization');
不用CORS还有没有别的办法呢?答案是有的。
3. WEB服务器反代
同源策略仅是针对浏览器的安全策略。服务器端调用HTTP接口只是使用HTTP协议,不需要同源策略,也就不存在跨域问题。
还是以Nginx为例,通过Nginx配置一个代理服务器域名与domain1相同但端口不同来做跳板,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域访问。
server {
listen 81;
server_name www.domain1.com;
location / {
proxy_pass http://www.domain2.com:8080; #反向代理
proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
index index.html index.htm;
}
}
最后还有一招精神胜利法,那就是
4. 关闭浏览器的同源策略
这招太损了,就不贴方法上来了。
小结
摸清楚跨域问题后发现其实它真的没那么可怕,只要搞清楚原理,一切都将迎刃而解。
参考
跨域资源共享 CORS 详解 - 阮一峰的网络日志 (ruanyifeng.com)
Nginx配置跨域请求 Access-Control-Allow-Origin * - SegmentFault 思否
加油
加油。。。。
jsonp,websocket的跨端方案也可以讲讲
我要hokori给我讲
???
感觉预检请求好像是近几年才能在浏览器控制台看到的
之前尝试搞评论框和某用图床放视频的时候也被跨域唬住了
但确实,方法总比困难多
每次都是需要使用的时候再重新去度娘一下。
emo了
很有精神!