Skip to content

跨域的方式

同源策略

协议、域名、端口都相同才同域

为什么浏览器不支持跨域

cookie localStorage dom元素也有同源策略 iframe ajax 也不支持跨域

实现跨域的方式

  • jsonp
  • cors
  • postMessage
  • document.domain
  • window.name
  • location.hash
  • http-proxy
  • nginx
  • websocket

jsonp

jsonp只能发送get请求 不支持post put delete 不安全 xss攻击

index.html

html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    function jsonp({ url, params, cb }) {
      return new Promise((resolve, reject) => {
        let script = document.createElement('script')
        window[cb] = function (data) {
          resolve(data)
          document.body.removeChild(script)
        }
        params = { ...params, cb }
        let arrs = []
        for (let key in params) {
          arrs.push(`${key}=${params[key]}`)
        }
        script.src = `${url}?${arrs.join('&')}`
        document.body.appendChild(script)
      })
    }
    jsonp({
      url: 'http://localhost:3000/test',
      params: { wd: '今天的新闻' },
      cb: 'show'
    }).then(data => {
      console.log(data);
    })
  </script>
</body>

</html>

server.js

初始化项目并安卓依赖

bash
npm init -y # 初始化项目
npm i express
node server.js # 启动服务
javascript
let express = require('express')
let app = express()

app.get('/test', function(req, res) {
  let {wd, cb} = req.query
  res.end(`${cb}('今天的新闻内容')`)
})

app.listen(3000)

cors

创建两个服务,从一个服务的index.html访问另一个服务的接口,模拟跨域

创建服务

server1.js

javascript
let express = require('express')
let app = express()

app.use(express.static(__dirname))

app.listen(3000)

server2.js

javascript
let express = require('express')
let app = express()

app.use(express.static(__dirname))

app.get('/getData', function (req, res) {
  console.log(req.headers);
  res.end('4000服务接口数据')
})

app.listen(4000)

index.html

html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    let xhr = new XMLHttpRequest
    xhr.open('GET', 'http://localhost:4000/getData', true)
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
          console.log(xhr.response);
        }
      }
    }
    xhr.send()
  </script>
</body>

</html>

启动服务

bash
nodemon server1.js
nodemon server2.js
访问 http://localhost:3000/index.html

报错
Access to XMLHttpRequest at 'http://localhost:4000/getData' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
index.html:21     GET http://localhost:4000/getData net::ERR_FAILED 200 (OK)

设置访问源

server2.js

diff
let express = require('express')
let app = express()

+let whiteList = ['http://localhost:3000']
+app.use(function(req, res, next) {
+  let origin = req.headers.origin
+  if (whiteList.includes(origin)) {
+    res.setHeader('Access-Control-Allow-Origin', origin)
+  }
+  next()
+})

app.use(express.static(__dirname))

app.get('/getData', function (req, res) {
  console.log(req.headers);
  res.end('4000服务接口数据')
})

app.listen(4000)

再次访问 http://localhost:3000/index.html可以拿到接口数据 http://localhost:3000/index.html

设置访问headers

index.html

diff
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    let xhr = new XMLHttpRequest
    xhr.open('GET', 'http://localhost:4000/getData', true)
+    xhr.setRequestHeader('name', 'lisi')  
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
          console.log(xhr.response);
        }
      }
    }
    xhr.send()
  </script>
</body>

</html>

访问 http://localhost:3000/index.html,报错

bash
Access to XMLHttpRequest at 'http://localhost:4000/getData' from origin 'http://localhost:3000' has been blocked by CORS policy: Request header field name is not allowed by Access-Control-Allow-Headers in preflight response.
index.html:22     GET http://localhost:4000/getData net::ERR_FAILED

sever2.js

diff
let express = require('express')
let app = express()

let whiteList = ['http://localhost:3000']
app.use(function(req, res, next) {
  let origin = req.headers.origin
  if (whiteList.includes(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin)
+    res.setHeader('Access-Control-Allow-Headers', 'name') // 设置多个 'name, age'
  }
  next()
})

app.use(express.static(__dirname))

app.get('/getData', function (req, res) {
  console.log(req.headers);
  res.end('4000服务接口数据')
})

app.listen(4000)

设置访问methods

index.html

diff
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    let xhr = new XMLHttpRequest
+    xhr.open('PUT', 'http://localhost:4000/getData', true)
    xhr.setRequestHeader('name', 'lisi')
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
          console.log(xhr.response);
        }
      }
    }
    xhr.send()
  </script>
</body>

</html>

访问 [http://localhost:3000/index.html](http://localhost:3000/index.html),报错

bash
Access to XMLHttpRequest at 'http://localhost:4000/getData' from origin 'http://localhost:3000' has been blocked by CORS policy: Method PUT is not allowed by Access-Control-Allow-Methods in preflight response.
index.html:22     PUT http://localhost:4000/getData net::ERR_FAILED
(anonymous) @ index.html:22

server2.js

diff
let express = require('express')
let app = express()

let whiteList = ['http://localhost:3000']
app.use(function(req, res, next) {
  let origin = req.headers.origin
  if (whiteList.includes(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin)
    res.setHeader('Access-Control-Allow-Headers', 'name') // 设置多个 'name, age'
+    res.setHeader('Access-Control-Allow-Methods', 'PUT')
  }
  next()
})

app.use(express.static(__dirname))

app.get('/getData', function (req, res) {
  console.log(req.headers);
  res.end('4000服务接口数据')
})
+app.put('/getData', function (req, res) {
+  console.log(req.headers);
+  res.end('4000服务接口数据')
+})

app.listen(4000)

减少options请求

请求跨域接口会先发送options请求,再发送真实请求

server2.js

diff
let express = require('express')
let app = express()

let whiteList = ['http://localhost:3000']
app.use(function(req, res, next) {
  let origin = req.headers.origin
  if (whiteList.includes(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin)
    res.setHeader('Access-Control-Allow-Headers', 'name') // 设置多个 'name, age'
    res.setHeader('Access-Control-Allow-Methods', 'PUT')
+    res.setHeader('Access-Control-Max-Age', 6) // 设置6s内不重复发options请求
+    if (req.method === 'OPTIONS') {
+      res.end() // options请求不做任何处理
+    }
  }
  next()
})

app.use(express.static(__dirname))

app.get('/getData', function (req, res) {
  console.log(req.headers);
  res.end('4000服务接口数据')
})
app.put('/getData', function (req, res) {
  console.log(req.headers);
  res.end('4000服务接口数据')
})

app.listen(4000)

允许cookie跨域

index.html

diff
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    let xhr = new XMLHttpRequest
+    document.cookie = 'name=lisi'
+    xhr.withCredentials = true
    xhr.open('PUT', 'http://localhost:4000/getData', true)
    xhr.setRequestHeader('name', 'lisi')
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
          console.log(xhr.response);
        }
      }
    }
    xhr.send()
  </script>
</body>

</html>

访问 [http://localhost:3000/index.html](http://localhost:3000/index.html),报错

bash
Access to XMLHttpRequest at 'http://localhost:4000/getData' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
index.html:24     PUT http://localhost:4000/getData net::ERR_FAILED
(anonymous) @ index.html:24

server2.js

diff
let express = require('express')
let app = express()

let whiteList = ['http://localhost:3000']
app.use(function(req, res, next) {
  let origin = req.headers.origin
  if (whiteList.includes(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin)
    res.setHeader('Access-Control-Allow-Headers', 'name') // 设置多个 'name, age'
    res.setHeader('Access-Control-Allow-Methods', 'PUT')
+    res.setHeader('Access-Control-Allow-Credentials', true)
    res.setHeader('Access-Control-Max-Age', 6) // 设置6s内不重复发options请求
    if (req.method === 'OPTIONS') {
      res.end() // options请求不做任何处理
    }
  }
  next()
})

app.use(express.static(__dirname))

app.get('/getData', function (req, res) {
  console.log(req.headers);
  res.end('4000服务接口数据')
})
app.put('/getData', function (req, res) {
  console.log(req.headers);
  res.end('4000服务接口数据')
})

app.listen(4000)

允许前端获取的header头

server2.js

diff
let express = require('express')
let app = express()

let whiteList = ['http://localhost:3000']
app.use(function(req, res, next) {
  let origin = req.headers.origin
  if (whiteList.includes(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin)
    res.setHeader('Access-Control-Allow-Headers', 'name') // 设置多个 'name, age'
    res.setHeader('Access-Control-Allow-Methods', 'PUT')
    res.setHeader('Access-Control-Allow-Credentials', true)
+    res.setHeader('Access-Control-Expose-Headers', 'name')
    res.setHeader('Access-Control-Max-Age', 6) // 设置6s内不重复发options请求
    if (req.method === 'OPTIONS') {
      res.end() // options请求不做任何处理
    }
  }
  next()
})

app.use(express.static(__dirname))

app.get('/getData', function (req, res) {
  console.log(req.headers);
  res.end('4000服务接口数据')
})
app.put('/getData', function (req, res) {
  res.setHeader('name', 'zhaoliu')
  console.log(req.headers);
  res.end('4000服务接口数据')
})

app.listen(4000)

index.html

diff
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    let xhr = new XMLHttpRequest
    document.cookie = 'name=lisi'
    xhr.withCredentials = true
    xhr.open('PUT', 'http://localhost:4000/getData', true)
    xhr.setRequestHeader('name', 'lisi')
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
          console.log(xhr.response);
+          console.log(xhr.getResponseHeader('name')); // zhaoliu
        }
      }
    }
    xhr.send()
  </script>
</body>

</html>

添加注释

server2.js

javascript
let express = require('express')
let app = express()

let whiteList = ['http://localhost:3000']
app.use(function(req, res, next) {
  let origin = req.headers.origin
  if (whiteList.includes(origin)) {
    // 设置哪些源访可以问我
    res.setHeader('Access-Control-Allow-Origin', origin)
    // 允许携带哪些头访问我
    res.setHeader('Access-Control-Allow-Headers', 'name') // 设置多个 'name, age'
    // 允许携带哪些方法访问我
    res.setHeader('Access-Control-Allow-Methods', 'PUT')
    // 允许携带cookie
    res.setHeader('Access-Control-Allow-Credentials', true)
    // 允许前端获取哪个头
    res.setHeader('Access-Control-Expose-Headers', 'name')
    // 预检的存活时间
    res.setHeader('Access-Control-Max-Age', 6) // 设置6s内不重复发options请求
    if (req.method === 'OPTIONS') {
      res.end() // options请求不做任何处理
    }
  }
  next()
})

app.use(express.static(__dirname))

app.get('/getData', function (req, res) {
  console.log(req.headers);
  res.end('4000服务接口数据')
})
app.put('/getData', function (req, res) {
  res.setHeader('name', 'zhaoliu')
  console.log(req.headers);
  res.end('4000服务接口数据')
})

app.listen(4000)

postMessge

a.js

javascript
let express = require('express')
let app = express()

app.use(express.static(__dirname))

app.listen(3000)

b.js

javascript
let express = require('express')
let app = express()

app.use(express.static(__dirname))

app.listen(4000)

a.html

html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <iframe src="http://localhost:4000/b.html" frameborder="0" id="frame" onload="load()"></iframe>
  <script>
    function load() {
      let frame = document.getElementById('frame')
      frame.contentWindow.postMessage('来自3000窗口的信息', 'http://localhost:4000')
      window.onmessage = function (e) {
        console.log(e.data);
      }
    }
  </script>
</body>

</html>

b.html

html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    window.onmessage = function (e) {
      console.log(e.data);
      e.source.postMessage('来自4000服务窗口的信息', e.origin)
    }
  </script>
</body>

</html>

启动服务

bash
nodemon a.js
nodemon b.js
访问 http://localhost:3000/a.html

控制台打印
来自3000窗口的信息     b.html:13 
来自4000服务窗口的信息  a.html:17

window.name

a和b是同域的 http://localhost:3000

c是独立的 http://localhost:4000 a获取c的数据(跨域) a先引用c,c把值放到window.name上,再把a引用的地址改到b(此时window.name还在)

a.js

javascript
let express = require('express')
let app = express()

app.use(express.static(__dirname))

app.listen(3000)

b.js

javascript
let express = require('express')
let app = express()

app.use(express.static(__dirname))

app.listen(4000)

a.html

html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <iframe src="http://localhost:4000/c.html" frameborder="0" onload="load()" id="iframe"></iframe>
  <script>
    let first = true
    function load() {
      if (first) {
        let iframe = document.getElementById('iframe')
        iframe.src = 'http://localhost:3000/b.html'
        first = false
      } else {
        console.log(iframe.contentWindow.name); // 来自c的信息
      }
    }
  </script>
</body>

</html>

b.html

空页面

c.html

html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    window.name = '来自c的信息'
  </script>
</body>

</html>

启动服务

bash
nodemon a.js
nodemon b.js
访问 http://localhost:3000/a.html
在a页面获取到了c的window.name
来自c的信息 a.html:25

location.hash

路径后面的hash值可以用来通信 目的a想访问c a传给c一个hash值,c收到hash值后,c把hash值传递给b,b将结果放到a的hash中

a.js

javascript
let express = require('express')
let app = express()

app.use(express.static(__dirname))

app.listen(3000)

b.js

javascript
let express = require('express')
let app = express()

app.use(express.static(__dirname))

app.listen(4000)

a.html

html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <iframe src="http://localhost:4000/c.html#data_from_a" frameborder="0"></iframe>
  <script>
    window.onhashchange = function () {
      console.log(location.hash);
    }
  </script>
</body>

</html>

b.html

html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    window.parent.parent.location.hash = location.hash
  </script>
</body>

</html>

c.html

html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    console.log(location.hash);
    let iframe = document.createElement('iframe')
    iframe.src = 'http://localhost:3000/b.html#data_from_c'
    document.body.appendChild(iframe)
  </script>
</body>

</html>

启动服务

nodemon a.js
nodemon b.js
访问 http://localhost:3000/a.html#data_from_c
在a页面的hash值被更新了
#data_from_a c.html:12 
#data_from_c a.html:17

domain(没成功)

通过设置相同父domain实现

a.js

javascript
let express = require('express')
let app = express()

app.use(express.static(__dirname))

app.listen(3000)

b.js

javascript
let express = require('express')
let app = express()

app.use(express.static(__dirname))

app.listen(4000)

a.html

html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  a网站
  <iframe src="http://b.wtm.cn:3000/b.html" frameborder="0" onload="load()" id="frame"></iframe>
  <script>
    document.domain = 'wtm.cn'
    function load() {
      console.log(frame.contentwindow.a);
    }
  </script>
</body>

</html>

b.html

html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  b网站
  <script>
    document.domain = 'wtm.cn'
    var a = 100
  </script>
</body>

</html>

本地设置hosts

html
127.0.0.1 a.wtm.cn
127.0.0.1 b.wtm.cn

启动服务

nodemon a.js
nodemon b.js
访问 http://a.wtm.cn:3000/a.html

websoket

server.js

javascript
let express = require('express')
let app = express()

let WebSocket = require('ws')
let wss = new WebSocket.Server({port: 3000})
wss.on('connection', function(ws) { 
  ws.on('message', function (data) {
    console.log(data);
    ws.send('data from server')
  })
})

socket.html

html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    // 高级api 不兼容 socket.io(一般使用它)
    let socket = new WebSocket('ws://localhost:3000')
    socket.onopen = function () {
      socket.send('data from front')
    }
    socket.onmessage = function (e) {
      console.log(e.data);
    }
  </script>
</body>

</html>

启动服务并访问

html
nodemon server.js
访问file:///Users/wtm/fe/code/fe/http/cors1/7.websocket/socket.html
控制台打印 data from server