Skip to content

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

跨域背景

浏览器出于安全考虑,有同源策略

  • 协议
  • 域名
  • 端口

👆 有一个不同就是跨域

安全考虑指的是 防止 CSRF 攻击(利用用户的登录态发起恶意请求)

假设没有同源策略

A 网站可以被任意其他来源的 Ajax 访问到内容

如果当前 A 网站还存在登录态

就可以通过 Ajax 获得登录态任何信息 当然跨域并不能完全阻止 CSRF

🤔 浏览器报错请求跨域了,那么请求到底发出去没有?

请求必然是发出去了,但是浏览器拦截了响应(没有拦截请求)

为什么表单的方式可以发起跨域请求,Ajax 就不行

因为归根结底,跨域是为了阻止用户读取到另一个域名下的内容,Ajax 可以获取响应,浏览器认为这不安全,所以拦截了响应

但是表单并不会获取新的内容,所以可以发起跨域请求

同时也说明了跨域并不能完全阻止 CSRF,因为请求毕竟是发出去了

注意❕ 前端的跨域,除了前端与后端通信会跨域被拦截,前端和前端通信也会跨域被拦截( iframe 嵌套时数据共享通信会被拦截)

解决跨域-JSONP与后端通信

利用 <script> 标签没有跨域限制的漏洞

指向一个需要访问的地址并提供一个回调函数来接收数据

👇 js请求立即执行全局作用域下名为的 jsonp 的 回调函数 callback

html
<script src="http://domain/api?param1=a&param2=b&callback=jsonp"></script>
<script>
  // js get请求 把数据放到这个函数的参数里
  function jsonp(data) {
  	console.log(data)
	}
</script>
  • 使用简单
  • 兼容性不错
  • 只限 get 请求

在开发中可能会遇到多个 JSONP 请求的回调函数名是相同的

👇 因此封装一个 JSONP 🔧 函数

js
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 案例

js
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 给浏览器

js
res.statusCode = 204 // 204 No content,表示请求成功,但响应报文不含实体的主体部分
res.setHeader('Content-Length', '0')
res.end()

解决跨域-document.domain与后端通信?

二级域名相同时,比如 a.test.comb.test.com

给页面添加

js
document.domain = 'test.com'

表示二级域名都相同就可以实现跨域

TODO: 具体怎么设置

解决跨域-postMessage与前端通信

前端跨域通信场景,不是前端和后端通信

用于获取嵌入页面中的第三方页面数据 一个页面发送消息,另一个页面判断来源并接收消息

js
// 发送消息端
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('验证通过')
  }
})

TODO: 其他解决跨域被拦截方法