跨域

78 阅读3分钟

前置知识

对于一个最基本的网站由下面组成,分别是协议,域名,端口,对于www.baidu.com/ 这个地址,这三部分分别是下面三部分 image.png

这个443是https默认端口号,而http默认端口号是80

而对于在前端在本地起的服务大多数是http://localhost:8000

  • 协议:http
  • 域名: localhost(指向本记)
  • 端口: 8000

什么是跨域

当一个网站访问另一个网站时,就有可能出现跨域

当协议域名端口这三部分不完全相同的时候就会发生跨域

举个例子:

假设你正在访问的网页地址是:https://www.example.com:443/home

要请求的地址是否同源原因
https://www.example.com:443/user同源三要素完全相同
https://api.example.com:443/data跨域域名不同 (www vs api)
http://www.example.com:443/home跨域协议不同 (https vs http)
https://www.example.com:8080/home跨域端口不同 (443 vs 8080)

记住:  localhost 和 127.0.0.1 即使都指向本机,在浏览器看来也是不同的域名,也会构成跨域。

为什么有跨域

为了安全

  1. 假设你登录了一个银行网站,此时他会保存你的登录凭证(比如token),
  2. 此时你不小心访问了一个恶意网站
  3. 如果没有跨域限制,他就能拿到你的token,然后用你的token去干坏事

直观感受跨域

<!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>
    <input class="ipt" />
    <button class="btn">Click Me</button>

    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script>
      console.log(axios);
      const btn = document.querySelector(".btn");
      const ipt = document.querySelector(".ipt");
      btn.addEventListener("click", () => {
        // alert(ipt.value);
        axios.get("http://localhost:3000/testXss").then((res) => {
          console.log(res.data);
        });
      });
    </script>
  </body>
</html>

这两个地址的域名和端口不相同就会发生跨域

如果直接点击按钮去请求就会失败

image.png

怎么解决跨域问题

1.cors

  • 原理: 端服务器在返回的 HTTP响应头 中,设置一些特殊的字段(如 Access-Control-Allow-Origin),明确告诉浏览器:“哪些外部的源可以访问我的资源”。
  • 示例: 如下我在后端api中设置了对所有请求都允许(会带来安全隐患),则可以正常访问跨域资源
router.get('/', function (ctx, next) {
  ctx.set('Access-Control-Allow-Origin', '*')
  ctx.body = 'this is a users response!'
})

2.jsonp

  • 原理:  利用 <script> 标签没有跨域限制的特性,通过动态创建标签来获取数据。但只支持 GET 请求,且不够安全现代,现在已不常用。

  • 示例 如下

// 前端代码
 <script>
        // 1. 定义全局回调函数
        function handleUserData(data) {
            document.getElementById('result').innerHTML = `
                用户名: ${data.name}, 年龄: ${data.age}
            `;
            console.log('收到数据:', data);
        }

        // 2. 动态创建script标签发起JSONP请求
        function requestUserData() {
            const script = document.createElement('script');
            // 指定后端地址,并告知回调函数名是 'handleUserData'
            script.src = 'http://localhost:3000/api/user?callback=handleUserData';
            document.body.appendChild(script); // 添加到页面后,浏览器立即开始加载
        }

        // 页面加载后执行
        requestUserData();
    </script>
    

   // 后端代码
   router.get('/', function (ctx, next) {
   const callbackName = ctx.query.callback; // 这里会是 'handleUserData'

    // 2. 要发送的数据
    const userData = {
        name: '张三',
        age: 25
    };

    // 3. 关键!返回一段 JavaScript 代码,而不是 JSON
    // 格式: "函数名(数据)"
    ctx.body = `${callbackName}(${JSON.stringify(userData)})`;

    // 设置 Content-Type 为 JavaScript
    ctx.type = 'text/javascript';
})

3.代理

  • 原理:  因为跨域是浏览器的限制,服务器与服务器之间没有这个限制。前端可以在本地或者用一个中间服务器设置一个代理。让前端的请求先发给这个同源的代理服务器,再由代理服务器去请求真正的目标服务器,最后将数据返回给前端。
  • 示例 如下,在server中配置代理
import { defineConfig } from 'vite'
import { fileURLToPath, URL } from 'node:url'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import vue from '@vitejs/plugin-vue'

// https://vite.dev/config/
export default defineConfig({
  resolve: {
      alias: {
          '@': fileURLToPath(new URL('./src', import.meta.url)),
      },
  },
  server: {
      open: true,
      proxy: {  // 配置代理
          '/api': {
              // target: 'http://127.0.0.1:4523/m1/3781196-0-default',
              target: 'http://localhost:3000',
              rewrite: path => path.replace(/^\/api/, ''),
          },
      },
  },
  plugins: [
    vue(), 
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),],
})