什么是跨域?为什么浏览器要使用同源策略?你有几种方式可以解决跨域问题?了解预检请求嘛?
跨域背景
浏览器出于安全考虑,有同源策略
- 协议
- 域名
- 端口
👆 有一个不同就是跨域
安全考虑指的是 防止 CSRF 攻击(利用用户的登录态发起恶意请求)
假设没有同源策略
A 网站可以被任意其他来源的 Ajax 访问到内容
如果当前 A 网站还存在登录态
就可以通过 Ajax 获得登录态任何信息 当然跨域并不能完全阻止 CSRF
🤔 浏览器报错请求跨域了,那么请求到底发出去没有?
请求必然是发出去了,但是浏览器拦截了响应(没有拦截请求)
为什么表单的方式可以发起跨域请求,Ajax 就不行
因为归根结底,跨域是为了阻止用户读取到另一个域名下的内容,Ajax 可以获取响应,浏览器认为这不安全,所以拦截了响应
但是表单并不会获取新的内容,所以可以发起跨域请求
同时也说明了跨域并不能完全阻止 CSRF,因为请求毕竟是发出去了
注意❕ 前端的跨域,除了前端与后端通信会跨域被拦截,前端和前端通信也会跨域被拦截(
iframe
嵌套时数据共享通信会被拦截)
解决跨域-JSONP与后端通信
利用 <script>
标签没有跨域限制的漏洞
指向一个需要访问的地址并提供一个回调函数来接收数据
👇 js请求立即执行全局作用域下名为的 jsonp
的 回调函数 callback
<script src="http://domain/api?param1=a¶m2=b&callback=jsonp"></script>
<script>
// js get请求 把数据放到这个函数的参数里
function jsonp(data) {
console.log(data)
}
</script>
- 使用简单
- 兼容性不错
- 只限 get 请求
在开发中可能会遇到多个 JSONP 请求的回调函数名是相同的
👇 因此封装一个 JSONP 🔧 函数
function jsonp(url, jsonpCallbackName, callbackFn = ()=>{}) {
// 1. 动态创建 js标签
let script = document.createElement('script')
script.src = url
script.async = true
script.type = 'text/javascript' // 创建一个script标签
// 2. 创建回调函数挂在全局作用域
window[jsonpCallbackName] = callbackFn
document.body.appendChild(script)
}
// 使用方式
jsonp('http://xxx', 'callback', function(value) {
console.log(value)
})
👆 不直接在 HTML
中写 js标签
请求,而是 工具函数
动态创建 js标签
并设置 回调函数
解决跨域-CORS通信与后端通信
CORS通信 需要浏览器和服务端同时支持
- 现代浏览器自动进行 CORS 通信?(IE 8 和 9 通过 XDomainRequest 来实现)
- 服务端设置
Access-Control-Allow-Origin
就可以开启 CORS。 设置通配符则表示所有网站都可以访问资源,或者白名单域名
CORS通信的请求会分为 简单请求、复杂请求
简单请求
当满足以下条件时,会触发简单请求
- 请求方法
METHOD
GET
HEAD
POST
Content-Type
text/plain
multipart/form-data
application/x-www-form-urlencoded
满足👆的 请求方法
和 Content-Type
请求中的任意 XMLHttpRequestUpload
对象均没有注册任何事件监听器 XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。
👆 TODO: 什么意思
复杂请求
不符合以上条件的请求就是复杂请求
此时会发起一个预检请求,该请求是 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
字段,没有就报错提醒
此时发现前端发起了复杂请求后,nodejs
服务返回结果永远是报错的
因为预检请求也会进入服务端请求会触发 next
方法
而预检请求并不包含 Authorization
字段,所以服务端会报错
解决办法在 nodejs服务端
中过滤 option
方法
👇 option
请求返回 204
给浏览器
res.statusCode = 204 // 204 No content,表示请求成功,但响应报文不含实体的主体部分
res.setHeader('Content-Length', '0')
res.end()
解决跨域-document.domain与后端通信?
二级域名相同时,比如 a.test.com
和 b.test.com
给页面添加
document.domain = 'test.com'
表示二级域名都相同就可以实现跨域
TODO: 具体怎么设置
解决跨域-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('验证通过')
}
})