平常的工作中,前端向后台发请求的时候,会遇到跨域的问题,这里就跨域问题进行梳理。
同源策略
讲跨域之前,都要讲一下同源策略。
同源策略,是浏览器最核心的安全功能。
同源策略: 同协议 + 同域名 + 同端口
- 协议:如
HTTP协议 - 域名:如
binnie.qq.com - 端口:如
80
举个例子:http://binnie.qq.com:80 与 http://binnie.qq.com:443 是不同源的,因为其端口不同。
讲完同源策略,就进入到本文的主题了,如果要在不同的源之间进行通信,那就要进行跨域,下面讲一下跨域常用几种方法。
1. JSONP
JSONP,跨域的一种经典模式,核心是使用script标签(因为script标签支持请求其他源的数据)
正常发起js请求是这样的,<script>可以直接拉到js的数据并且直接执行。
<script src="/jquery.js"></script>
JSONP的请求时这样的,请求地址携带callback参数,该参数为数据回调函数,后台回的数据格式为cbFun(res),把回包做为函数的参数返回。
<!-- 前端 -->
<script>
function cbFun(res) {
console.log(res) // 请求拿到的数据
}
</script>
<script src="/request?callback=cbFun"></script>
后台也要配合处理,接收请求时获取callback参数,回包时将数据包裹为callback的参数中转为字符串返回。
app.use(async ctx => {
var res = {
ret: 0,
msg: 'ok',
data: 'Hello World'
}
ctx.body = ctx.query.callback + '(' + JSON.stringify(res) + ')'
});
JSONP是跨域的经典操作,当然,jquery的ajax对JSONP做了处理,前端请求添加dataType:'jsonp'(数据返回格式)即可发起JSONP请求。
$.ajax({
url: '/request',
type: 'GET',
dataType: 'jsonp',
success: function(res) {
console.log(res)
}
})
当然,因为JSONP是利用<script>标签实现的,所以只能发送get请求。
2. CORS(跨域资源共享)
CORS,W3C的标准,通过对response & request headers的设置,允许进行跨域的请求。
CORS请求分为两种:简单请求 & 非简单请求
简单请求
当请求同时符合以下两规则时,请求为简单请求。
- 请求类型为:
HEAD | GET | POST - Content-Type:
text/plain | multipart/form-data | application/x-www-form-urlencoded
发送CORS请求的时候,浏览器会在request headers增加一个Origin字段,这个字段就是网站的源(协议+域名+端口)
Origin: 'http://binnie.qq.com'
服务器跟Origin对应的是 Access-Control-Allow-Origin,该属性表示服务器支持的源是哪些,也可以是*全部支持。
Access-Control-Allow-Origin: 'http://binnie.qq.com'
由于请求是跨域的,所以cookie是携带不过去的,如果想携带本域的cookie,请求中就要多带一个字段 withCredentials
withCredentials: true
服务器跟withCredentials对应的是Access-Control-Allow-Credentials,表示是否允许携带cookie。如果需要添加这个配置,那么Access-Control-Allow-Origin不允许为*,只能设置为确定的域名
Access-Control-Allow-Origin: 'http://binnie.qq.com'
Access-Control-Allow-Credentials: true
非简单请求
不符合简单请求的,就都是非简单请求。
非简单请求与简单请求不不同之处在于,简单请求一条请求就搞定,非简单请求需要两条。
- 第一条:预检请求(OPTIONS)
- 第二条:真正的请求
预检请求
预检请求,作用是先询问服务器,之后的请求的域、请求类型、携带headers有哪些,服务器通过之后,就会发送真正的请求,服务器不通过则不会发真正的请求。
预检Headers会携带以下几个信息
Origin: 'http://binnie.qq.com' // 源
Access-Control-Request-Method: 'PUT' // 真正请求的方法
Access-Control-Request-Headers: 'binnie' // 有扩展Header
服务器与预检请求的对应
Access-Control-Allow-Origin: 'http://binnie.qq.com' // 允许的源
Access-Control-Allow-Methods: 'GET,POST,PUT' // 允许的请求方法
Access-Control-Allow-Headers: 'binnie' // 允许的扩展Header
真正的请求
预检请求通过之后,其实真正的请求就可以请求成功了,这时request Headers携带Origin字段。
CORS与JSONP的对比这里也可以看出来,CORS支持的请求类型更加丰富。
3. postMessage
postMessage为HTML5的新特性,支持iframe之间互相发送信息,由于iframe之间可以不同域,所以postMessage也是跨域的一种实现。
4. window.name
window.name与postMessage的跨域其实是同种类型,在页面中,父页面与iframe都是共享同一个window.name的,并且都有读写权限。
5.document.domain
document.domain可以设置为父域,但是不能设置为其他。
6.webSocket
webSocket,浏览器不对其做同源限制,所以直接就可以跨域。
写在最后
前后台对接的时候,语气好的话可能不用跨域,当然跨域的问题也经常会出现,熟悉之后会发现跨域请求还是很容易实现的。