常见的跨域解决方法
1、JSONP (JSON with Padding)
由于浏览器同源策略的限制,网页中无法通过 Ajax 请求非同源的接口数据。但是script标签不受浏览器同源策略的影响,可以通过 src 属性,请求非同源的 js 脚本。
因此,JSONP 的实现原理,就是通过 script 标签的 src 属性,请求跨域的数据接口,并通过函数调用的形式,接收跨域接口响应回来的数据。
注意:是script标签中src属性,要有函数
一个简单的JSONP
先定义一个 success 回调函数,再在url中?后面调用
<script>
function success(data) {
console.log('获取到了data数据:')
console.log(data)
}
</script>
<script src="http://ajax.frontend.itheima.net:3006/api/jsonp?callback=success&name=zs&age=20"></script>
//服务器端响应数据必须是一个函数的调用,真正要发送给客户端的数据需要作为函数调用的参数。
const data = 'fn({name: "张三", age: "20"})';
res.send(data);
缺点
由于 JSONP 是通过 script 标签的 src 属性,来实现跨域数据获取的,所以,JSONP 只支持 GET 数据请求,不支持 POST 请求。
注意:JSONP 和 Ajax 之间没有任何关系,不能把 JSONP 请求数据的方式叫做 Ajax,因为 JSONP 没有用到 XMLHttpRequest 这个对象。
jQuery中的JSONP
$.ajax({
url: 'http://ajax.frontend.itheima.net:3006/api/jsonp?name=zs&age=20',
// 如果要使用 $.ajax() 发起 JSONP 请求,必须指定 datatype 为 jsonp
dataType: 'jsonp',
success: function(res) {
console.log(res)
}
})
默认情况下,使用 jQuery 发起 JSONP 请求,会自动携带一个 callback=jQueryxxx 的参数,jQueryxxx 是随机生成的一个回调函数名称。
$.ajax({
url: 'http://ajax.frontend.itheima.net:3006/api/jsonp?name=zs&age=20',
dataType: 'jsonp',
// 发送到服务端的参数名称,默认值为 callback
jsonp: 'callback',
// 自定义的回调函数名称,默认值为 jQueryxxx 格式
jsonpCallback: 'abc',//参数以及回调函数名称
success: function(res) {
console.log(res)
}
})
jQuery 中的 JSONP,也是通过 <script>
标签的 src 属性实现跨域数据访问的,只不过,jQuery 采用的是动态创建和移除 <script>
标签的方式,来发起 JSONP 数据请求。
在发起 JSONP 请求的时候,动态向 <header>
中 append 一个 <script>
标签;
在 JSONP 请求成功以后,动态从 <header>
中移除刚才 append 进去的 <script>
标签;
2、CORS
CORS:全称为 Cross-origin resource sharing,即跨域资源共享,它允许浏览器向跨域服务器发送 Ajax 请求,克服了 Ajax 只能同源使用的限制。
CORS 需要浏览器和后端同时支持。IE 8 和 9 需要通过 XDomainRequest
来实现。
浏览器会自动进行 CORS 通信,实现 CORS 通信的关键是后端。只要后端实现了 CORS,就实现了跨域。
服务端设置 Access-Control-Allow-Origin
就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。
Node 服务器端设置响应头示例代码
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');//类似Access-Control-Allow-Origin: 'http://localhost:3000'
res.header('Access-Control-Allow-Methods', 'GET, POST');
next();
})
withCredentials属性
在使用Ajax技术发送跨域请求时,默认情况下不会在请求中携带cookie信息。
withcredentials:指定在涉及到跨域请求时。是否携带cookie信息,默认值为false
Access-Control-Allow-Credentials: true 允许客户端发送请求时携带cookie
虽然设置 CORS 和前端没什么关系,但是通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求和复杂请求。
简单请求
1:使用下列方法之一:GET
、 HEAD
、 POST
2:Content-Type 的值仅限于下列三者之一: text/plain
、 multipart/form-data
、 application/x-www-form-urlencoded
。
请求中的任意 XMLHttpRequestUpload
对象均没有注册任何事件监听器; XMLHttpRequestUpload
对象可以使用 XMLHttpRequest.upload
属性访问。
复杂请求
对于复杂请求来说,首先会发起一个预检请求,该请求是 option
方法的,通过该请求来知道服务端是否允许跨域请求。
对于预检请求来说,如果你使用过 Node 来设置 CORS 的话,可能会遇到过这么一个坑。
以下以 express 框架举例:
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*')
res.header('Access-Control-Allow-Methods', 'PUT, GET, POST, DELETE, OPTIONS')
res.header(
'Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept, Authorization, Access-Control-Allow-Credentials'
)
next()
})
//过滤 option 方法
res.statusCode = 204
res.setHeader('Content-Length', '0')
res.end()
该请求会验证你的 Authorization
字段,没有的话就会报错。
当前端发起了复杂请求后,你会发现就算你代码是正确的,返回结果也永远是报错的。因为预检请求也会进入回调中,也会触发 next
方法,因为预检请求并不包含 Authorization
字段,所以服务端会报错。
想解决这个问题很简单,只需要在回调中过滤 option
方法即可
3、document.domain
该方式只能用于二级域名相同的情况下,比如 a.test.com
和 b.test.com
适用于该方式。
只需要给页面添加 document.domain = 'test.com'
表示二级域名都相同就可以实现跨域
4、postMessage
这种方式通常用于获取嵌入页面中的第三方页面数据。一个页面发送消息,另一个页面判断来源并接收消息
// 发送消息端
window.parent.postMessage('message', 'http://test.com')
// 接收消息端
var mc = new MessageChannel()
mc.addEventListener('message', event => {
var origin = event.origin || event.originalEvent.origin
if (origin === 'http://test.com') {
console.log('验证通过')
}
})