跨域


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

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

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

1. JSONP

JSONP 的原理很简单,就是利用

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('验证通过')
   }
})