什么是跨域访问?
由于浏览器同源策略,凡是发送请求url的协议、域名、端口三者之间任意一个与当前页面地址不同即为跨域。存在跨域的情况:
- 网络协议不同,如http协议访问https协议。
- 端口不同,如80端口访问8080端口。
- 域名不同,如qianduanblog.com访问baidu.com。
- 子域名不同,如abc.qianduanblog.com访问def.qianduanblog.com。
- 域名和域名对应ip,如www.a.com访问20.205.28.90.
- 请求的数据是XHR(XMLHttpRequest)浏览器会有跨域的限制。如果不是XHR类型的则不会限制
我们的浏览器需要禁止跨域呢?
这里举两个常见的例子:
1.我们知道一帮浏览器都使用cookie去储存我们的用户登录信息。如果允许跨域访问,那么别的网站只需要一段脚本就可以获取你的cookie,从而冒充你的身份去登录网站,造成非常大的安全问题。
2.假设现在有两个不同域,如果没有这一安全策略,那么当用户在访问a.com时,a.com的一段脚本就可以在不加载b.com的页面而随意修改或者获取b.com上面的内容。这样将会导致b.com页面的页面发生混乱。
跨域的解决办法
其实跨域的解决办法从本质上去区分,可以划分为两类。
第一类:被调用方
第二类:调用方
接下来我们就开始基于这两种类型去解决跨域问题。
被调用方的解决办法
这里在说明代码之前,我们要先知道在我们的请求中可以分为两种请求:(1)简单请求(2)非简单请求
简单请求:
在heard里面无定义头
Content-Type为一下几种:
text/plain
multipart/from-data
application/x-www-form-urlencoded
常见非简单请求:
put,delete方法的ajax请求
发送json格式的ajax请求
带自定义头的ajax请求复制代码
简单请求与非简单请求的区别
简单请求:浏览器会先自行后判断
非简单请求:浏览器先判断后执行(OPTIONS)
复制代码
因此我们在访问非简单请求的时候,我们可以看到发送的方法会是opttions。意思就是服务器先询问服务是否存在相关资源。当存在相关资源的时候才能正常返回。
接下来我们就可以开展被调用方的解决方案:
解决方案1:JSONP
什么是JSONP?
Jsonp(JSON with Padding) 是 json 的一种"使用模式",可以让网页从别的域名(网站)那获取资料,即跨域读取数据。
下面我们展示JSONP的使用案例
#这里我们模仿ajax进行一个跨越访问,其datatype需要设置为jsonp
#里面的jsonp,设置回调函数的名称为callback
<script type="text/javascript">
$.ajax({
url:"http://crossdomain.com/services.php",
dataType:'jsonp',
data:'',
jsonp:'callback',
success:function(result) {
for(var i in result) {
alert(i+":"+result[i]);//循环输出a:1,b:2,etc.
}
},
timeout:3000
});
</script>
复制代码
#这是后台返回数据的方法
#可以看出其实就是把返回的数据再包含在与前端的回调函数的名字一样的函数体即可
function api_jsonp_encode($json)
{
if (!empty($_GET['callbak'])) {
return $_GET['callbak'] . '(' . $json . ')'; // jsonp
}
return $json; // json
}复制代码
jsonp其实作为一种跨域的解决手段其实存在比较明显的缺陷:
1.服务器需要改动代码
2.只支持get方法
3.发送的不是XHR复制代码
解决方法2:增加header头
设置头文件:
//设置请求域名
header('Access-Control-Allow-Origin:*');
//设置请求头
header("Access-Control-Allow-Headers:Origin, X-Requested-With, Content-Type, Accept, Authorization");
//设置请求方法
header('Access-Control-Allow-Methods:GET, POST, PUT,DELETE,OPTIONS,PATCH');
header('Access-Control-Allow-Methods:*');
//设置预检命令的缓存头
header('Access-Control-Max-Age:3600');
#这里说明一下,*号代表没有限制全匹配。因此你可以看到们设置的请求域名,还有请求方法都是有*号配置。
#设置御检命令缓存头就是让浏览器当发送一次御检命令后,后期就可以在缓存时间内直接使用缓存,而不需要再次请求。其第二个参数为缓存时间
复制代码
但是我们直接上述的配置,只能满足一般情况。因为如果当跨域访问的时候携带cookie,或者自定义头的时候我们还是不能成功跨域的因此。针对这两种情况,我们可以做出以下的调整修改
带Cookie的跨域
//设置允许携带Cookie
header("Access-Control-Allow-Credentials: true");
//需要设置与服务器匹配的域名,不能使用*
header('Access-Control-Allow-Origin:http://www.xxx.com');
#如果可能需要匹配多个允许域名,可以参考下面的动图做法
//通过系统变量获取origin
$origin = isset($_SERVER['HTTP_ORIGIN'])? $_SERVER['HTTP_ORIGIN'] : '';
//设置允许数组列表
$allow_origin = array(
'http://client1.runoob.com',
'http://client2.runoob.com'
);
//判断是否存在于数组中
if(in_array($origin, $allow_origin)){
header('Access-Control-Allow-Origin:'.$origin);
}
复制代码
自定义请求头的跨域处理
一般情况下可以使用以下的代码设置请求头
header("Access-Control-Allow-Headers:Origin, X-Requested-With, Content-Type, Accept, Authorization");
但是如果不生效。可以把对应的请求头也写进去例如:
header("Access-Control-Allow-Headers:Origin, X-Requested-With, Content-Type, Accept, Authorization,test-header");
复制代码
以上就是通过被调用方设置header头去解决对应的跨域问题。
但是上述的方法还是存在一定的局限性。因为加header头本质上还是会改动了我们的代码。那么我们还有没有更好的方式呢?其实我们可以基于服务器的改造
解决方法3:配置服务器的
ngix的配置
server {
listen 80; #监听80端口
server_name demo.com; #本地域名
location / {
proxy_pass http://XXXX.com; #把所有请求都转发到此需要提供服务的端口下
add_header Access-Control-Allow-Headers $http_access_control_request_headers';
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Methods *;
add_header Access-Control-Max-Age 3600;
add_header Access-Control-Allow-Credentials true;
#判断如果进入的是与预检查
if ($request_method = OPTIONS){
return 200;
}
}
}
复制代码
.apache配置
//修改vhost文件
<virtualhost *:80="">
DocumentRoot "C:/htdocs/demo" #目录路径
ServerName demo.com #域名
##ErrorLog "logs/dummy-host.localhost-error.log"
##CustomLog "logs/dummy-host.localhost-access.log" common
#设置转发(需要开启 proxy_module,proxy_http)
ProxyPass/ http://XXXX.com
#把请求头的Access-Controller-Request-Headerss值返回到Access-Control-Allow-Headers
Header always set Access-Control-Allow-Headers "expr=%{req:Access-Controller-Request-Headers}"
#把请求头的orgin值返回到Access-Control-Allow-Orgin
Header always set Access-Control-Allow-Orgin "expr=%{req:orgin}"
Header always set Access-Control-Allow-Methods "*"
Header always set Access-Control-Allow-Credentials "true"
Header always set Access-Control-Max-Age "3600"
#处理预检命令OPTIONS,直接返回204(开启 headers_module,rewrite_module)
RewriteEngine On
RewriteCond%{REQUEST_METHOD}OPTIONS
RewriteRule^(.*)$"/"[R=204,L]
</virtualhost>
复制代码
以上的服务器配置就是用于跨域请求的被调用方的配置
#我们刚刚讨论完了被调用方的设置。那么我们再来说一下调用方的设置。因为在我们实际的开发中,有时候被调用方有可能是不配合我们进行跨域访问的修改的,那么我们只能自己解决跨域问题了。
在一般开发中我们解决的思路就是使用反向代理。而配置反向代理只能通过我们的服务器去访问对方的服务器,从而绕过浏览器的这一个槛。
nginx:
server {
listen 80; #监听80端口
server_name demo.com; #本地域名
#后期所有的调用接口都需要使用该地址
#例如:我们访问http://aa.com/index/login
#改为:/ajaxserver/index/login
location/ajaxserver {
proxy_pass http://XXXX.com; #需要跨域访问接口的的地址
}
}复制代码
<virtualhost *:80="">
DocumentRoot "C:/htdocs/demo" #目录路径
ServerName demo.com #域名
##ErrorLog "logs/dummy-host.localhost-error.log"
##CustomLog "logs/dummy-host.localhost-access.log" common
#后期所有的调用接口都需要使用该地址
#例如:我们访问http://aa.com/index/login
#反向代理设置
ProxyPass /ajaxserver http://XXXX.com
</virtualhost>复制代码