平常的工作中,前端向后台发请求的时候,会遇到跨域的问题,这里就跨域问题进行梳理。
同源策略
讲跨域之前,都要讲一下同源策略。
同源策略,是浏览器最核心的安全功能。
同源策略: 同协议 + 同域名 + 同端口
- 协议:如
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
,浏览器不对其做同源限制,所以直接就可以跨域。
写在最后
前后台对接的时候,语气好的话可能不用跨域,当然跨域的问题也经常会出现,熟悉之后会发现跨域请求还是很容易实现的。