跨域(Cross-Origin)指的是浏览器的同源策略
(Same-Origin Policy)限制了一个源(域、协议和端口)下的 JavaScript 脚本访问另一个源的资源。为了保护用户数据的安全,浏览器默认禁止网页向其他域发送请求或访问资源,这种限制称为跨域限制。
1. 同源策略
同源策略要求以下三者相同才算同源:
- 协议:如
http
和https
- 域名:如
example.com
- 端口:如
:80
和:443
例如:
http://example.com
和https://example.com
是跨域的(协议不同)。http://example.com
和http://sub.example.com
是跨域的(域名不同)。http://example.com:80
和http://example.com:8080
是跨域的(端口不同)。
2. 常见的跨域场景
- API 请求:前端应用需要向不同的 API 域名请求数据,例如从
http://localhost:3000
请求http://api.example.com
。 - CDN 资源加载:网站从其他域名的 CDN 加载静态资源。
- 嵌套页面:通过
<iframe>
加载不同源的网页内容。
3. 解决跨域问题的方法
(1)CORS(跨域资源共享)
W3C标准,服务器可以通过设置 Access-Control-Allow-Origin
头部来允许跨域请求。CORS 是目前最主流的跨域解决方案,允许服务器设置哪些域可以访问其资源。IE不能低于IE10。主要用于服务端的配置
。需要浏览器和服务器端同时支持。
可以使用中间件:cors
示例:
Access-Control-Allow-Origin: http://example.com
//或者
"Access-Control-Allow-Origin": "*" //使用通配符,任何地址都可以访问,但安全性不高。
实例:
建立一个新的文件夹,使用 npm init 生成 package.json, 安装依赖
pnpm install koa -D #koa模块
pnpm install koa-router -D # 路由模块
pnpm install koa2-cors -D # 中间件模块
npm install -g nodemon #是一个实用程序,它监视 Node.js 应用程序中的文件变化,并在检测到变化时自动重新启动应用程序。它非常适合开发环境,因为它节省了手动重启服务器的时间和精力。 热启动
app.js
/*
1、http: Node.js 的核心模块,提供基本的 HTTP 功能,如创建服务器、处理请求和响应。它较为底层,使用时需要手动处理很多细节。
2、Express: 基于 http 的框架,简化了 HTTP 服务器的构建。它提供了更高层的 API,使路由、请求处理和中间件的使用更加便捷。适合快速开发 RESTful API 和 web 应用。
3、Koa: 由 Express 的创建者开发,是一个更轻量级且现代化的框架。Koa 使用 async/await 语法,允许更优雅的错误处理和中间件链管理。它的设计目标是更小、更强大、可扩展性更高。
*/
const Koa = require('koa')
const cors = require('koa2-cors') //中间件
const app = new Koa()
const index = require('./router/index')
//实际开发中会配置域名白名单,只允许这些域名发起请求。
app.use(cors({
origin: '*', // 相当于Access-Control-Allow-Origin:允许所有源,或者你可以指定特定的源如 'http://example.com'
allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
allowHeaders: ['Content-Type', 'Authorization', 'Accept'],
}))
//将路由注册到应用。
app.use(index.routes(), index.allowedMethods())
//启动监听
app.listen(3001,()=>{
console.log('http://localhost:3001')
})
在router目录下index.js
const Router = require('koa-router')
const router = new Router()
router.prefix('/api')
router.get('/index',ctx=>{
return (ctx.body={
code: 200,
message: 'ok'
})
})
//http://localhost:3001/api/index
/*
router.get('/', async (ctx) => {
ctx.body = 'Welcome to Koa!';
});
*/
module.exports = router
启动:
(2)JSONP(JSON with Padding)
JSONP 是一种较旧的跨域方式,适用于 GET
请求。它通过 <script>
标签加载数据,将响应包装为 JavaScript 函数调用。他是基于src 不受同源策略限制。
<!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>
<div id="result"></div>
<div>ddfdsf</div>
</body>
<script>
window.onload = function(){
let obj,s
//obj = {'table':'products', 'limit':10} //添加参数
s = document.createElement('script') //动态创建script
//指定请求的 URL,并在 URL 中添加 callback 参数
//相当于callbackFunctions({'table':'products', 'limit':10})
//s.src = 'https://www.runoob.com/try/ajax/jsonp.php?jsoncallback=callbackFunctions' + JSON.stringify(obj);
s.src = 'https://www.runoob.com/try/ajax/jsonp.php?jsoncallback=callbackFunctions'
document.body.appendChild(s)
}
function callbackFunctions(data){
console.log('ok')
console.log(data)
document.getElementById('result').innerHTML = JSON.stringify(data)
}
</script>
</html>
使用Vite proxy 或者 node代理 或者 webpack proxy 他们三种方式都是代理
(3)代理服务器
通过设置代理服务器,浏览器请求首先发送到同源的代理服务器,再由代理服务器请求目标域,从而实现跨域。
某些服务器可能在配置上允许任何来源访问资源,即在响应头中设置 Access-Control-Allow-Origin: *。这种配置允许来自任何来源的跨域请求,无需浏览器进行额外的权限校验。不过,这通常限于不包含敏感信息的资源,以防止安全隐患。不需要设置跨域,也能访问。
server: {
proxy: {
// 字符串简写写法
'/foo': 'http://localhost:4567',
// 选项写法
'/api': {
target: 'http://jsonplaceholder.typicode.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
},
// 正则表达式写法
'^/fallback/.*': {
target: 'http://jsonplaceholder.typicode.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/fallback/, '')
},
// 使用 proxy 实例
'/api': {
target: 'http://jsonplaceholder.typicode.com',
changeOrigin: true,
configure: (proxy, options) => {
// proxy 是 'http-proxy' 的实例
}
}
}
}
// vite.config.js
import {
defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [vue()],
server: {
proxy: {
// 当请求以 /api 开头时,代理到 https://api.example.com
'/api': {
target: 'https://api.example.com',
changeOrigin: true, // 允许跨域
//用于重写路径。例如,将/api前缀去除,
//使得请求发送到https://api.example.com/, 而不是https://api.example.com/api。
rewrite: (path) => path.replace(/^\/api/, ''), // 将 /api 去掉
},
},
},
});
使用:
mport axios from 'axios';
export function fetchData() {
// 在代码中调用API时,只需使用配置的代理路径即可,不需要指定完整的后端地址
return axios.get('/api/data'); // 只需要使用 /api,Vite 会自动代理
}
server:{
proxy: {
'/API/zs_zdbs.php':{
target:'https://xiaoapi.cn', //https://xiaoapi.cn/API/zs_zdbs.php
changeOrigin: true,
}
}
},
使用
axios.get('/API/zs_zdbs.php?h=24')
.then(res=>{
console.log(res.data)
})
(4)后端设置
可以在服务端设置 Nginx 或其他代理服务器,通过将跨域请求代理到目标服务器,避免浏览器的跨域限制。
Apache 配置(在虚拟主机配置文件中添加):
<VirtualHost *:80>
ServerName yourdomain.com
ProxyPass /api/ http://api.example.com/
ProxyPassReverse /api/ http://api.example.com/
Header set Access-Control-Allow-Origin "*"
Header set Access-Control-Allow-Methods "GET, POST, OPTIONS"
Header set Access-Control-Allow-Headers "Origin, Content-Type, Accept, Authorization"
<Location "/api/">
SetEnvIf Request_Method OPTIONS OPTIONS_REQUEST
Header set Access-Control-Allow-Origin "*"
Header set Access-Control-Allow-Methods "GET, POST, OPTIONS"
Header set Access-Control-Allow-Headers "Origin, Content-Type, Accept, Authorization"
Header always set Access-Control-Max-Age "1000"
Header always set Content-Length "0"
Header always set Content-Type "text/plain charset=UTF-8"
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{
REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=200,L]
</IfModule>
</Location>
</VirtualHost>
(5)WebSocket
WebSocket 不受同源策略限制,可以实现跨域通信,但需要服务器和客户端的支持。
4. 跨域的常见错误和影响
- No ‘Access-Control-Allow-Origin’ header:当服务器未设置允许的源时,浏览器会阻止请求。
- 预检请求(Preflight Request)失败:对于复杂请求(如
PUT
、DELETE
等),浏览器会首先发送预检请求,验证服务器是否允许该请求。
5. 总结
跨域是浏览器出于安全考虑限制不同源之间资源请求的机制。通过 CORS、JSONP、代理等方式可以实现跨域通信。