跨域
涉及面试题:什么是跨域?为什么浏览器要使用同源策略?你有几种方式可以解决跨域问题?了解预检请求嘛?
- 因为浏览器出于安全考虑,有同源策略。也就是说,如果协议、域名或者端口有一个不同就是跨域,
Ajax
请求会失败。 - 那么是出于什么安全考虑才会引入这种机制呢? 其实主要是用来防止
CSRF
攻击的。简单点说,CSRF
攻击是利用用户的登录态发起恶意请求。 - 也就是说,没有同源策略的情况下,
A
网站可以被任意其他来源的Ajax
访问到内容。如果你当前A
网站还存在登录态,那么对方就可以通过Ajax
获得你的任何信息。当然跨域并不能完全阻止CSRF
。
然后我们来考虑一个问题,请求跨域了,那么请求到底发出去没有? 请求必然是发出去了,但是浏览器拦截了响应。你可能会疑问明明通过表单的方式可以发起跨域请求,为什么
Ajax
就不会。因为归根结底,跨域是为了阻止用户读取到另一个域名下的内容,Ajax
可以获取响应,浏览器认为这不安全,所以拦截了响应。但是表单并不会获取新的内容,所以可以发起跨域请求。同时也说明了跨域并不能完全阻止CSRF
,因为请求毕竟是发出去了。
接下来我们将来学习几种常见的方式来解决跨域的问题
JSONP
JSONP
的原理很简单,就是利用标签没有跨域限制的漏洞。通过
标签指向一个需要访问的地址并提供一个回调函数来接收数据当需要通讯时
<script src="http://domain/api?param1=a¶m2=b&callback=jsonp"></script>
<script>
function jsonp(data) {
console.log(data)
}
</script>
JSONP
使用简单且兼容性不错,但是只限于get
请求。
在开发中可能会遇到多个
JSONP
请求的回调函数名是相同的,这时候就需要自己封装一个JSONP
,以下是简单实现
function jsonp(url, jsonpCallback, success) {
let script = document.createElement('script')
script.src = url
script.async = true
script.type = 'text/javascript'
window[jsonpCallback] = function(data) {
success && success(data)
}
document.body.appendChild(script)
}
jsonp('http://xxx', 'callback', function(value) {
console.log(value)
})
CORS
CORS
需要浏览器和后端同时支持。IE 8
和9
需要通过XDomainRequest
来实现。- 浏览器会自动进行
CORS
通信,实现CORS
通信的关键是后端。只要后端实现了CORS
,就实现了跨域。 - 服务端设置
Access-Control-Allow-Origin
就可以开启CORS
。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。 虽然设置CORS
和前端没什么关系,但是通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求和复杂请求。
简单请求
以
Ajax
为例,当满足以下条件时,会触发简单请求
- 使用下列方法之一:
GET
HEAD
POST
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()
})
- 该请求会验证你的
Authorization
字段,没有的话就会报错。 - 当前端发起了复杂请求后,你会发现就算你代码是正确的,返回结果也永远是报错的。因为预检请求也会进入回调中,也会触发
next
方法,因为预检请求并不包含Authorization
字段,所以服务端会报错。
想解决这个问题很简单,只需要在回调中过滤
option
方法即可
res.statusCode = 204
res.setHeader('Content-Length', '0')
res.end()
CORS 如何跨域发送 Cookie ?
客户端方面
- 设置
withCredentials
为true
即可让该跨域请求携带 Cookie。 注意携带的是目标页面所在域的 Cookie。
服务端方面
设置
Access-Control-Allow-Credentials
响应头为true
, 即可允许跨域请求携带 Cookie。除了
Access-Control-Allow-Credentials
之外,跨域发送 Cookie 还要求Access-Control-Allow-Origin
不允许使用通配符。 事实上不仅不允许通配符,而且只能指定单一域名因此,服务器可能需要维护一个接受 Cookie 的 Origin 列表, 验证
Origin
请求头字段后直接将其设置为Access-Control-Allow-Origin
的值
← 原生事件 URL1—输入 URL 和游览器 →