跨域

涉及面试题:什么是跨域?为什么浏览器要使用同源策略?你有几种方式可以 解决跨域问题?了解预检请求嘛?

  • 因为浏览器出于安全考虑,有同源策略。也就是说,如果协议、域名或者端口有⼀个不同 就是跨域, Ajax 请求会失败。
  • 那么是出于什么安全考虑才会引⼊这种机制呢? 其实主要是用来防止 CSRF 攻击的。简单点说, CSRF 攻击是利用用户的登录态发起恶意请求。
  • 也就是说,没有同源策略的情况下, A 网站可以被任意其他来源的 Ajax 访问到内容。
    如果你当前 A 网站还存在登录态,那么对方就可以通过 Ajax 获得你的任何信息。当然 跨域并不能完全阻止 CSRF

然后我们来考虑⼀个问题,请求跨域了,那么请求到底发出去没有?
请求必然 是发出去了,但是浏览器拦截了响应。
你可能会疑问明明通过表单的方式可以 发起跨域请求,为什么 Ajax 就不会。
因为归根结底,跨域是为了阻止用户读取到另⼀个域名下的内容, Ajax 可以获取响应,浏览器认为这不安全,所以拦截了响应。
但是表单并不会获取新的内容,所以可以发起跨域请求。同时也 说明了跨域并不能完全阻止 CSRF ,因为请求毕竟是发出去了。

1. JSONP

JSONP 的原理很简单,就是利用 <script> 标签没有跨域限制的漏洞。通过 <script> 标签指向⼀个需要访问的地址并提供⼀个回调函数来接收数据 当需要通讯时

<script src="http://domain/api?param1=a&param2=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)
})

2. CORS

  • CORS 需要浏览器和后端同时⽀持。 IE 8 和 9 需要通过 XDomainRequest 来实现。
  • 浏览器会自动进行 CORS 通信,实现 CORS 通信的关键是后端。只要后端实现了 CORS ,就实现了跨域。
  • 服务端设置 Access-Control-Allow-Origin 就可以开启 CORS 。
    该属性表示哪些域名 可以访问资源,如果设置通配符则表示所有网站都可以访问资源。
    虽然设置 CORS 和前端 没什么关系,但是通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求和复杂请求。

简单请求: 以 Ajax 为例,当满足以下条件时,会触发简单请求,

  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'
     )
     next()
  })
  • 该请求会验证你的 Authorization 字段,没有的话就会报错。
  • 当前端发起了复杂请求后,你会发现就算你代码是正确的,返回结果也永远是报错的。
    因为预检请求也会进入回调中,也会触发 next 方法,因为预检请求并不包含 Authorization 字段,所以服务端会报错。
    想解决这个问题很简单,只需要在回调中过滤 option 方法即可。
res.statusCode = 204
res.setHeader('Content-Length', '0')
res.end()

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('验证通过')
   }
})
posted @ 2022-01-04 16:54  ·灯  阅读(31)  评论(0编辑  收藏  举报