先来看下几个概念
跨域问题:浏览器在请求不同域的资源时,会因为同源策略(SOP)的影响而请求不成功,这就是通常所说的跨域问题。
同源策略:来自wiki的解释
In computing, the same-origin policy is an important concept in the web application security model. Under the policy, a web browser permits scripts contained in a first web page to access data in a second web page, but only if both web pages have the same origin. An origin is defined as a combination of URI scheme, hostname, and port number.[1][2] This policy prevents a malicious script on one page from obtaining access to sensitive data on another web page through that page's Document Object Model.
对比URL | 结果 | 结果 |
---|---|---|
http://www.example.com/dir/page2.html |
同源 | 相同的协议,主机,端口 |
http://www.example.com/dir2/other.html |
同源 | 相同的协议,主机,端口 |
http://username:password@www.example.com/dir2/other.html |
同源 | 相同的协议,主机,端口 |
http://www.example.com:81/dir/other.html |
不同源 | 相同的协议,主机,端口不同 |
https://www.example.com/dir/other.html |
不同源 | 协议不同 |
http://en.example.com/dir/other.html |
不同源 | 不同主机 |
http://example.com/dir/other.html |
不同源 | 不同主机(需要精确匹配) |
http://v2.www.example.com/dir/other.html |
不同源 | 不同主机(需要精确匹配) |
http://www.example.com:80/dir/other.html |
看情况 | 端口明确,依赖浏览器实现 |
常见的跨域解决方式
- jsonp
最常用的就是利用$.ajax指定dataType为jsonp,虽然JSONP在跨域ajax请求方面有很强的能力,但是它也有一些缺陷。首先,它没有关于JSONP调用的错误处理,一旦回调函数调用失败,浏览器会以静默失败的方式处理。其次,它只支持GET请求,这是由于该技术本身的特性所决定的。
- document.domain
目前,很多大型网站都会使用多个子域名,而浏览器的同源策略对于它们来说就有点过于严格了。如,来自www.a.com想要获取document.a.com中的数据。只要基础域名相同,便可以通过修改document.domain为基础域名的方式来进行通信,但是需要注意的是协议和端口也必须相同。
document.a.com中通过设置
document.domain = 'a.com';
www.a.com中设置:
document.domain = 'a.com';
var iframe = document.createElement('iframe');
iframe.src = 'http://document.a.com';
iframe.style.display = 'none';
document.body.appendChild(iframe);
iframe.onload = function() {
var targetDocument = iframe.contentDocument || iframe.contentWindow.document;
//可以操作targetDocument
}
推荐一个使用iframe跨域的库https://github.com/jpillora/xdomain
- Nginx反向代理
web2.0时代前后端的分离越来越流行,前后端项目单独部署使得项目更加灵活。举例,前端项目域名为 www.a.com,服务端项目域名为 api.a.com,此时前端请求服务端的时候必然会出现跨域问题,此时可以通过nginx反向代理+域名二级目录的形式解决。
前端设置服务端请求地址为:www.a.com/service/
server {
listen 80;
server_name www.a.com;
index index.html index.htm index.php;
root /Data/wwwroot/my-project;
location /service/ {
rewrite /service/(.*) /$1 break;
proxy_pass http://api.a.com;
proxy_set_header Host $proxy_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
server {
listen 80;
server_name api.a.com;
root /Data/wwwroot/my-api-project/public;
access_log /usr/local/var/log/nginx/access.log main;
index index.php index.html index.htm;
location / {
#autoindex on;
include /usr/local/etc/nginx/conf.d/php-fpm;
if (-f $request_filename/index.html){
rewrite (.*) $1/index.html break;
}
if (-f $request_filename/index.htm){
rewrite (.*) $1/index.htm break;
}
if (-f $request_filename/index.php){
rewrite (.*) $1/index.php;
}
if (!-f $request_filename){
rewrite (.*) /index.php;
}
}
}
通过nginx的proxy模块,将/service/下的所有请求转发到 api.a.com 上,通过 www.a.com/service/ 隐藏了真正的server地址
- 跨域资源共享CORS(Cross-origin resource sharing)
CORS的核心思想是通过一系列新增的HTTP头信息来实现服务器和客户端之间的通信,
浏览器发送一个带有Orgin字段的HTTP请求头,用来表明请求来源。服务器的Access-Control-Allow-Origin响应头表明该服务器允许哪些源的访问,一旦不匹配,浏览器就会拒绝资源的访问。
浏览器会将CORS请求分为两种:简单请求、非简单请求
简单请求:
- 请求方法只允许:GET,HEAD,POST
- 对于请求头字段有严格的要求,一般情况下不会超过以下几个字段: Accept、Accept-Language、Content-Language、Content-Type
- 当发起POST请求时,只允许Content-Type为application/x-www-form-urlencoded,multipart/form-data,text/plain。
对于前后端分离的项目,服务端和客户端通过RESUful Api通信时,dataType为json的ajax请求显然不是简单请求(Content-Type为application/json),浏览器对于非简单请求会先发送一个类型为options的预请求,options请求的作用是先测试下接口能否返回200,如果不是,则后面真正的get或post请求会丢弃。
针对非简单请求来说,由于每个请求都会发送预请求,这就导致接口数据的返回会有所延迟,时间被加长。所以,在使用CORS的过程中,可以采用一些方案来优化请求,将非简单请求转换成简单请求,从而提高请求的速度。