跨域是指从一个域名的网页去请求另一个域名的资源。比如,从www.a.com域名的网页去请求www.b.com域名的资源,只要协议、域名、端口有任何一个不同,都被当作是不同的域,跨域问题通常由浏览器的同源策略引起的。
同源策略就是浏览器出于安全考虑而制定的,例如数据安全,服务器安全,减少 xss(跨站脚本攻击),CSRF(跨站请求伪造) 等攻击。所谓同源,就是协议,域名,端口号都相同才能请求数据。如果是非同源请求发送后,浏览器会拦截响应。
  1. 浏览器出于安全考虑(数据安全,服务器安全,减少 xss,CSRF 攻击)  2. https:      协议      子域名     主域名     端口号   路径  3. 非同源,请求发送后,浏览器会拦截响应
跨域解决方案
1.jsonp
- 借助 script 标签 src 属性不受同源策略的限制(经常要加载第三方库等),来发送请求
- 后端将数据作为 callback 函数的实参,返回给前端一个 callback 的调用形式
- 浏览器接收到 callback 的调用会自动执行全局的callback函数
// 前端<button onclick="handle()">请求</button>
<script>    function jsonp(url, cb) {      return new Promise((resolve, reject) => {
        const script = document.createElement('script')
                window[cb] = function (data) {          // console.log(data) // 后端返回的数据          resolve(data)        }
        script.src = `${url}?cb=${cb}`
        // 将标签加到document.body时,浏览器就会发url请求        document.body.appendChild(script)          // callback('hello world')      })    }
    function handle() {      jsonp('http://localhost:3000', 'callback').then(res => {        console.log(res)      })    } </script>
前端通过一个jsonp函数,传入请求的url和回调函数的名称cb,然后在jsonp函数里面创建一个script标签,将该标签的src属性变为url后面用?拼接传入的字符串。// 后端const http = require('http');
http.createServer((req, res) => {  // 获取前端传过来参数  const query = new URL(req.url, `http://${req.headers.host}`).searchParams
  if (query.get('cb')) {    const cb = query.get('cb')  // 'callback'    const data = 'hello world'    const result = `${cb}("${data}")`   // "callback('hello world')"    res.end(result)  }
}).listen(3000);
后端创建一个http服务器,解析前端传过来的callback字符串参数,将数据作为参数返回"callback('hello world')"格式的一个函数。
缺点:
2. CORS(跨资源共享)
服务器在响应头中后端设置 Access-Control-Allow-Origin: '域名白名单',告诉浏览器允许哪个源进行跨域访问。可以是具体的域名,也可以是*表示允许所有源。如下
const http = require('http')
const server = http.createServer((req, res) => {  res.writeHead(200, {    'Access-Control-Allow-Origin': '*',        // 也可自行设置指定的域名可以访问     // 例如: 'Access-Control-Allow-Origin': 'http://192.168.2.1:5500'  })  res.end('hello world');})
server.listen(3000)
3.nginx反向代理
前端服务器和后端服务器不在同一个域名下, 前端服务器通过nginx 反向代理来访问后端服务器。
服务器和服务器之间的通信不存在跨域,可以开一台中间服务器(nginx),后端无需改变。前端把请求发给nginx , nginx 服务器把请求转发给后端的服务器,后端的服务器响应给 nginx 服务器,nginx 服务器加上响应头以后,再返回给前端,如下;
4. node 中间件代理
原理同nginx 反向代理,只不过多写一个node后端,前端服务器和后端服务器不在同一个域名下,前端服务器通过 node 中间件来访问后端服务器。
5. websocket
- 传统的前后端通信是基于http协议的,是单向的,只能从一端发到另一端,无法双向通信
- websocket 是基于tcp协议的,是双向的,可以从一端发送到另一端,也可以从另一端发送到一端
- socket协议一旦建立连接,就可以一直保持通信状态,不需要每次都建立连接,但是会更开销性能
// 前端<script>    function WebSocketTest(url, params = {}) {      return new Promise((resolve, reject) => {
                const socket = new WebSocket(url)
        // 当连接打开时发送数据        socket.onopen = () => {          socket.send(JSON.stringify(params))        }
        // 接收到后端的消息时打印数据并解决Promise        socket.onmessage = (event) => {          console.log(event.data)          resolve(event.data)        }      })    }
    WebSocketTest('ws://localhost:3000', { age: 18 }).then(res => {      console.log(res)    })  </script>
// 后端
const WebSocket = require('ws');
// 在 3000 端口上建立 WebSocket 伺服器 (随时都在线的服务)const ws = new WebSocket.Server({ port: 3000 });
let count = 0
// 监听连接ws.on('connection', (obj) => {  // console.log(obj);  obj.on('message', (msg) => {  // 收到客户端发来的消息    // console.log(msg.toString());   // 客户端传过来的数据    obj.send('收到了')
    setInterval(() => {      count++      obj.send(count)    }, 2000)
  })})
6. postMessage
当父级页面和iframe页面不在同一个域名下,他们之间的数据传输也存在跨域问题,父级页面和iframe页面之间可以通过posMessage来通信。
<h2>首页</h2><iframe id="frame" src="http://127.0.0.1:5500/%E8%B7%A8%E5%9F%9F/postMessage/detail.html"frameborder="0" width="800" height="500"></iframe>
<script>    let obj = { name: '阿杰', age: 18 }
    document.getElementById('frame').onload = function () {      this.contentWindow.postMessage(obj, 'http://127.0.0.1:5500') 
      window.onmessage = function (e) { // 接收iframe发送的消息        console.log(e.data);      }
    }  </script>
<h3>详情页 --- <span id="title"></span></h3>
<script>    const title = document.getElementById("title");    window.onmessage = function (e) {        // console.log(e.data);      title.innerText = e.data.age;      e.source.postMessage("阿杰 20了", e.origin)  // 向父级页面发送消息    }  </script>
7.document.domain
通过设置document.domain来允许同一主域名下的跨域通信,原理同postMessage,但是谷歌禁止了这种方法
总结
跨域:是指从一个域名的网页去请求另一个域名的资源。只要协议、域名、端口有任何一个不同,都被当作是不同的域。跨域问题通常由浏览器的同源策略引起,同源策略是为了保证用户信息的安全,防止恶意网站窃取数据
同源策略:浏览器出于安全考虑(数据安全,服务器安全,减少 xss,CSRF 攻击)而制定的一种只有协议、域名、端口号都相同才能请求数据的规定,非同源请求发送后,浏览器会拦截响应。
跨域方案:
- jsonp(script标签的src属性不受同源策略的限制)
- CORS(跨资源共享,通知浏览器哪些域名可以访问)
- websocket(socket协议可以保持长时间的连接,不受同源的限制,天生可以跨域)
- postMessage(父级页面和iframe页面之间可以通过posMessage来通信)
- document.domain(通过设置来允许同一主域名下的跨域通信,谷歌禁止了)
常见的解决方案有:CORS适用于需要支持多种HTTP方法(如GET、POST、PUT等)的现代Web应用,nginx 反向代理适用于前后端分离的项目,可以在服务器层面统一处理跨域问题。
该文章在 2025/2/21 16:01:37 编辑过