什么是跨域
什么是跨域呢,对于从未听过的人来说,是一个听起来挺唬人名字。其实也没什么,完全可以通过字面意思去揣摩,坦白讲,就是跨越不同的区域,就这个意思,当然为什么要去不同的区域呢,因为要拿哪里的资源呀,嘿嘿嘿嘿…。
咳咳,正经点啊,用官话来说:跨域,什么是跨域呢?
就是指一个域名下的网站去请求另一个域名下网站的资源。为什么或出现跨域这种事情呢。原因就在于。浏览器的同源策略,就是为了保密隐私,为了自身的安全。并且ajax本身禁止发送跨域请求
跨域分以下几种情况:
- http://www.acb.com -------ajax----- >http://uu.abc.com 域名级别不同
- http://hr.abc.com -------ajax----- >http://oa.abc.com 域名不同
- http://localhost:3000 -------ajax----- > http://localhast:8080 端口不同
- http://baidu.com -----ajax-----> https://baidu.com 协议不同
协议不同的本质还是端口不同,https是443端口,http是80端口 - http://localhost ------ajax----> http://127.0.0.1 这个是个规定,规定自己本机下的这种访问方式为跨域。
以上这几种情况的ajax请求均属于跨域请求。
知道了什么是跨域,那么就该去解决这问题,不然,我们怎么去拿到我们想要的数据呢。提供两种解决方案:
1.JSONP实现跨域
先说说什么是JSONP,JSON with Padding,填充式JSON,它是将其他网站上的JSON资料,作为参数填充,填充到哪里嫩?,其实使用jsonp时,后端会接受到一个参数,这个参数其实就是前端定义好了的一个处理函数的函数名,这个处理函数,需要一些数据,它将这些数据呢以外部传参的形式呈现出来,,而我们也说了,jsonp请求的时候会将这个函数名获取到,我们在使用一些拼接手法,将参数和函数名结合起来形成一个完整函数名加参数的形式,然后返回给客户端,客户端就可以得到自己所需的参数,又因为数据是以json的格式传输的,就形成了这种填充式json的感觉,这也就是jsonp名字的来由,不得不说,这个方法是真的秒啊。 最后在加上动态添加script标签,数据到了前端后,就会自动调用这个函数。
至于前端是怎么请求的呢,当然是通过script标签的src啦,为什么要用script标签的src呢,你有没有发现你在引用第三方插件时,不论是网上的url,还是本地的url,script它都能找到啊,有木有??简直比隔壁老王翻墙的本事都厉害啊…有木有。为什么呢?难不成,他真的是跟隔壁老王学得,而且青出于蓝胜于蓝。 不仅如此哦,html标签还有几个也可能是跟老王学的,例如iframe、style、还有img标签,它的src也可以,emmm…这几个不学好。
开个玩笑哈,为什么呢,这是浏览器给的特权,是浏览器动态的访问外域文件的一种方式,结果被大家给利用了,真的是单纯的孩子。利用script的src这一特点,便可以实现跨域访问。当使用script标签进行外域资源请求时,就会将后端定义好的代码下载到本地执行。
**2018-11-22日 — 补充**
具体过程:
- 前端通过script标签发起资源请求,参数中携带定义好的函数名。
- 后端会将获取到的函数名以及所需的参数作为回发数据,响应至客户端,被客户端下载,客户端下载到代码后,将得到的函数及参数添加到新的script标签中,就相当于执行之前定义的函数,只是,函数调用以及传参是由后端发过来的。
相信你已经大致了解了这个过程,兴许你还能发现jsonp的一些缺陷,即它的请求方式类似于get请求,不能进行post请求,如果要想向服务端post数据,会报请求格式不正确错误,并且如果有,那么这种方式的数据都在url中,如果post一堆数据,url也会显得冗余,拼一长串也不太现实。并且如果要保存登录状态,这种方式是无法携带认证信息,jsonp是绕过了ajax请求请求资源的机制,转而使用src的特性获取资源,后端不能获取一致的session,无法进行登录状态的检查,然后就是jsonp的安全性问题,它无法设置请求限制,要是服务器不值得信赖,可能会下载到恶意的执行代码,例如重定向到非法网站盗取用户信息,或者简单点的拿到一个死循环代码,那不就凉凉了。
所谓的padding其实是指返回来的函数及参数,在此作以修正。
都退后,我要开始装B了, 等一等,我想多说几种方案。论一论他们的优缺点。
- 方案一:
服务端:将要返回的数据,填充进一条字符串格式的js语句中,组成一条正确的可执行的js语句,在返回到客户端。
客户端:添加script标签,“<script src=“服务端地址”>”
结果script标签可以跨域访问,请求到服务端返回的js语句,并立刻执行。
例如服务端,返回的数据可以这样写
const http = require('http');
const url = require('url');
http.createServer(function(req,res){
var params = url.parse(req.url,true);
console.log(params);//在方案三时有用
res.writeHead(200,{
"Content-Type":"application/json;charset=utf-8"
});
res.write(`alert("疼")`);
res.end();
}).listen(8080,()=>{
console.log("Jsonp server is running");
});
客户端,我未设路由,所以直接写服务端地址即可
<script src="http://127.0.0.1:8080"></script>
客户端加载网页时便会弹出警告框。这只是一个简单的实现。我们并不满足于此。
- 方案二:
服务端:返回一条自定义函数的调用语句,客户端必须执行指定名称的函数
客户端:提前定义一个与服务端返回的同名的函数,有一个参数可以接受服务端的数据。
例如:
服务端:
var data = "Welcom";
res.write(`fun("${data}")`);
res.end();
客户端:
<script>
function fun(data){
alert(data);
}
</script>
<script type="text/javascript" src="http://127.0.0.1:8080?fun"></script>
函数定义要在返回数据的script标签之前进行,这里的结果也会弹出警示框,并且携带了数据。但是美中不足的是,函数名是写死的,不可能说我们在请求数据前,先跟服务端商量好,函数名用那个,那不搞笑呢么。
- 方案三
这里我们可以从前端请求时预先在参数内设定一个callback参数,像这样。
<script type="text/javascript" src="http://127.0.0.1:8080?callback=fun"></script>
为什么这样,因为我们在后端接收请求时,可以获取到callback参数,通过对url部分转对象后可以获取
var params = url.parse(req.url,true);
console.log(params.query.callback);
这是获取到的部分
query: { callback: 'fun' },
以此便可以使后端不受前端的限制,前端可以随意定义自身所需的处理函数,并且在客户端通过js代码动态的创建script标签,并为其src属性设置好请求的url。服务端也不用在乎前端定义的是什么函数名了。
操作如下:
服务端:
const http = require('http');
const url = require('url');
http.createServer(function(req,res){
var params = url.parse(req.url,true);
console.log(params);
console.log(params.query.callback);
res.writeHead(200,{
"Content-Type":"application/json;charset=utf-8"
})
var data = "Welcom";
if(params.query.callback){
var str3 = `${params.query.callback}("${data}")`;//定义要送函数名和函数参数
console.log(str3);
res.end(str3);
}else{
res.end();
}
//var data = "Welcom";
//res.write(`fun("${data}")`);
//res.end();
}).listen(8080,()=>{
console.log("Jsonp server is running");
});
客户端:
<script src="js/jquery-1.11.3.js"></script>
<script>
//用jq的方式创建标签,原生js也可以
$('#btn1').on('click',function(){
$(`<script type='text/javascript' src='http://127.0.0.1:8080?callback=fun'>`).appendTo('body');
});
function fun(data){
alert(data);
$('body>script:last-child').remove();//每次请求删除上一次请求的script标签,防止script标签因为请求次数不断的添加。
}
</script>
<script type="text/javascript" src="http://127.0.0.1:8080?callback=fun"></script>
好了,最后一种方案是相对于前两种方案的优化,推荐使用
2. CORS,此方法需要服务端与客户端的配合
CORS跨域资源共享
服务端的响应头设置:
res.writeHead(200,{
“Content-Type”:“application/json;charset=utf-8”,
"Access-Control-Allow-Origin”:"*" | " 指定的来源域名"
})
这样即可, 设置为"*"的方式会不限制任何域名的访问,当然也可以指定允许访问的来源域名,这种方式虽然简单,但并不安全。
最后说一下JQ中实现JSONP跨域的方式,
$.ajax({
//在1.x版本中
url:" ... ",
type: "get"|"post",
data:"变量=值&..."|{变量:值,...},
dataType:"json",//通过这句将其设为jsonp,便会实现jsonp跨域
success:function(res){ //在响应成功结束后自动
//res就是响应的结果
},
error(){ 当请求出错时,自动触发 },
complete(){ 无论成功还是出错,只要请求结束就执行 }
})
//在 3.x版本中,实现了promise
.then(function(res){
...
})
使用JQ进行jsonp跨域请求,不需要我们设置函数名,jq会随机生成函数名,也会自动清除每次请求产生的script标签,极大的放便了操作。其原理与方案三是一致的。
OK,完结,撒花
并未结束 **2018/11/22日补充**
之前的跨域设置针对简单跨域,例如只设置了
Access-Control-Allow-Origin: ”*“
的跨域,先说说简单跨域和复杂跨域:
简单跨域,即get、post、head请求中的一种,并且头信息也有几项限制,最突出的一点就是Content-Type只能为:application/x-www-form-urlencoded,multipart/form-data和text/plain中的一种
复杂跨域:不是以上两种情况的,
对于复杂跨域,还需要设置一些相关信息。若要进行服务端验证或者携带cookie还需要在客户端和服务端配合设置。
由于跨域请求默认不发送Cookie和HTTP认证信息。相信应该明白,同源策略的目的,就是为了保密,为了防止数据被盗取,cookie中携带着用户很多的信息,所以这好似服务端与客户端两个人之间的小秘密,不能被别人知道了,万一被截取就可能发生跨域伪造请求的问题。但是实际生活中,我们经常会进行在线验证等等,不能所有情况下都要重新登录,这时就要保证服务端的完全信任了,然后携带cookie,如果非要把Cookie发到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials字段:Access-Control-Allow-Credentials: true,另一方面,开发者在发起ajax请求时设置withCredentials为true。
在这里,当我们的服务器设置Access-Control-Allow-Credentials: true时,会产生新的问题,在浏览器标准中,当服务器中设置Access-Control-Allow-Credentials为true时,Access-Control-Allow-Origin不能设置为*,而Access-Control-Allow-Origin: *是我们常用的解决跨域问题的设置。
此问题的解决方案有两种,第一种方案是简单的设置一个白名单;另一种方案,如果之前设置Access-Control-Allow-Origin: *,此时可以在服务器配置文件进行设置:先获取发起跨域请求的源域,然后设置Access-Control-Allow-Origin的值为获取到的源域。当然这个设置可能在后端某些配置文件里,也可能直接在服务器配置文件设置。但思路一致。
具体实现:
客户端说一下以下几种条件下的使用:
默认跨域不携带cookie,所以需要声明允许携带cookie。
(1) 原生js中设置
xhr.withCredentials=true;
(2)jq方式的在配置参数中设置
xhrFields:{
withCredentials:true
},
(3)在axios中的设置
import axios from 'axios';
//设置允许携带cookie
axios.defaults.withCredentials=true;
服务端的配置,仅讨论nodejs环境的配置方式,其他服务器的配置信息也一致
(1)设置响应头,允许跨域携带认证信息:
我这里使用express的框架
res.header(“Access-Control-Allow-Credentials”, true);
(2)在允许跨域携带认证信息后,
Access-Control-Allow-Origin",不能为“*”,需要设置允许的源域名,或者可以自己设置一个白名单,允许这些可以跨域访问。
res.header(“Access-Control-Allow-Origin”, “http://localhost:8080”);
这样就可以实现,携带认证信息的进行访问。