Skip to content

跨域

浏览器出于安全原因会实施同源策略(Same-Origin Policy),默认情况下网页脚本只能访问与其来源相同的域名、端口和协议下的资源

Cross-Origin 网页脚本视图访问另一个域名下的资源

  • CORS需要浏览器和服务器同时支持,浏览器自动完成,服务器需要实现CORS接口

TIP

同源策略限制跨域脚本能力、隐私保护、防止恶意攻击

  • 无法读取非同源的 cookie、Storage、indexDB
  • 无法读取非同源DOM 防止恶意网站通过 iframe 其他网站获取数据

CORS 预检请求

跨域中非简单请求时,浏览器会自动发出一个预检请求(OPTIONS),Access-Control-Request-HeadersAccess-Control-Request-MethodOrigin 从而获取服务端是否允许该资源跨域

预检请求

简单请求

  • 请求方法 HEAD GET POST
  • 请求头 Accept Accept-Language Content-Language Content-Type : (text/plain multipart/form-data application/x-www-form-urlencoded)

非简单请求

先发送 OPTIONS 预检请求,获取服务器允许跨域后,再发送跨域请求

  • 请求方法 PUT DELETE
  • 请求头 Content-Type: application/json 等 (Content-Type 需要服务器设置对应的)

TIP

默认情况,跨域请求不会发送身份凭证信息Cookie,如果需要发送凭证信息,需要设置

  • 客户端xhr.withCredentials = true
  • 服务端响应头添加 Access-Control-Allow-Credentials: true,(否则响应会被忽略)

服务端设置跨域

测试项目

js
app.use((req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000') // 跨域来源
  res.setHeader('Access-Control-Allow-Methods', 'GET,HEAD,PUT,PATCH,POST,DELETE') // 跨域方法
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type') // 跨域header
  res.setHeader('Access-Control-Allow-Credentials', 'true') // 跨域请求携带 cookie
  next()
})
app.use((req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000') // 跨域来源
  res.setHeader('Access-Control-Allow-Methods', 'GET,HEAD,PUT,PATCH,POST,DELETE') // 跨域方法
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type') // 跨域header
  res.setHeader('Access-Control-Allow-Credentials', 'true') // 跨域请求携带 cookie
  next()
})

JSONP

html
<script>
    function myFunction (data) {
        alert('获取数据成功,2s后改变数据!')
        let p = document.getElementsByTagName('p')[0]
        setTimeout(function () {
            p.innerHTML = data.message
        }, 2000)
        // 2s后p标签内的内容将改变
    }
</script>
<script src="http://localhost:3001?callback=myFunction"></script>
<script>
    function myFunction (data) {
        alert('获取数据成功,2s后改变数据!')
        let p = document.getElementsByTagName('p')[0]
        setTimeout(function () {
            p.innerHTML = data.message
        }, 2000)
        // 2s后p标签内的内容将改变
    }
</script>
<script src="http://localhost:3001?callback=myFunction"></script>
js
// server.js
app.get('/', (req, res) => {
  const callbackName = req.query.callback // myFunction
  res.send(`${callbackName}({'message': 'hello world from JSONP!🙃'});`)
  // myFunction({'message': 'hello world from JSONP!'})
  // 一个带参数的执行函数
})
// server.js
app.get('/', (req, res) => {
  const callbackName = req.query.callback // myFunction
  res.send(`${callbackName}({'message': 'hello world from JSONP!🙃'});`)
  // myFunction({'message': 'hello world from JSONP!'})
  // 一个带参数的执行函数
})