前置知识
对于一个最基本的网站由下面组成,分别是协议,域名,端口,对于www.baidu.com/
这个地址,这三部分分别是下面三部分
- 协议:https
- 域名:www.baidu.com
- 端口:443
这个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 即使都指向本机,在浏览器看来也是不同的域名,也会构成跨域。
为什么有跨域
为了安全
- 假设你登录了一个银行网站,此时他会保存你的登录凭证(比如token),
- 此时你不小心访问了一个恶意网站
- 如果没有跨域限制,他就能拿到你的token,然后用你的token去干坏事
直观感受跨域
- 用node起了一个本地服务(http://localhost:3000/) (node+koa)
- 写个请求的demo,地址(http://127.0.0.1:5500/index.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>
<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>
这两个地址的域名和端口不相同就会发生跨域
如果直接点击按钮去请求就会失败
怎么解决跨域问题
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()],
}),],
})