跨域问题源于浏览器的同源策略(Same-Origin Policy),这是现代Web安全体系的核心机制之一。当浏览器端发起的请求,其目标服务器的协议(http/https)、域名或端口三者中任意一个与当前页面所在服务器不一致时,就会触发跨域限制。
典型跨域场景包括:
同源策略的核心目的是保护用户身份凭证(如Cookie、Session)不被恶意网站滥用。其安全逻辑基于以下关键点:
同源策略本质上是一种"数据读取权限控制"机制,而不是"请求拦截"机制。这是理解跨域问题的关键认知——浏览器永远不会阻止请求的发送,只会控制响应数据是否对JS可见。
需要特别注意同源策略的精确作用范围:
被限制的行为:
不被限制的行为:
CORS(Cross-Origin Resource Sharing)是W3C制定的跨域标准解决方案,其核心思想是:将跨域访问的控制权交给服务器,浏览器只负责执行规则校验。
满足以下所有条件的请求被视为简单请求:
简单请求的处理流程:
http复制Access-Control-Allow-Origin: https://yourdomain.com
不满足简单请求条件的请求会触发预检(Preflight)机制:
http复制Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
http复制Access-Control-Allow-Origin: https://yourdomain.com
Access-Control-Allow-Methods: PUT
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Max-Age: 86400
| 响应头 | 作用 | 注意事项 |
|---|---|---|
| Access-Control-Allow-Origin | 允许的源 | 带Cookie时不能为* |
| Access-Control-Allow-Methods | 允许的方法 | 预检请求必需 |
| Access-Control-Allow-Headers | 允许的自定义头 | 预检请求必需 |
| Access-Control-Allow-Credentials | 是否允许凭证 | true/false |
| Access-Control-Max-Age | 预检缓存时间 | 单位秒 |
| Access-Control-Expose-Headers | 暴露给JS的响应头 | 默认只暴露简单头 |
当需要跨域携带Cookie时,必须满足以下条件:
前端显式开启凭证模式:
javascript复制// Fetch API
fetch(url, { credentials: 'include' })
// XHR
xhr.withCredentials = true
后端响应头必须配置:
http复制Access-Control-Allow-Origin: https://exact.domain.com // 不能是*
Access-Control-Allow-Credentials: true
Cookie属性要求:
生产环境推荐方案,通过服务器中转避开浏览器限制:
nginx复制server {
listen 80;
server_name yourdomain.com;
location /api/ {
proxy_pass http://api-server.com;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# CORS headers (可选)
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Credentials true;
}
}
优势:
传统跨域方案,利用script标签无跨域限制的特性:
javascript复制function handleResponse(data) {
console.log(data);
}
const script = document.createElement('script');
script.src = 'https://api.example.com/data?callback=handleResponse';
document.body.appendChild(script);
限制:
跨窗口通信的官方方案:
javascript复制// 发送方
targetWindow.postMessage(data, 'https://target.com');
// 接收方
window.addEventListener('message', (event) => {
if (event.origin !== 'https://trusted.com') return;
console.log(event.data);
});
适用场景:
浏览器遵循以下规则自动管理Cookie:
| 属性值 | 跨站请求携带行为 | 适用场景 |
|---|---|---|
| Strict | 完全不携带 | 高敏感操作 |
| Lax | GET请求携带 | 默认值 |
| None | 允许携带 | 需配合Secure |
常见问题1:Cookie未按预期携带
排查步骤:
常见问题2:代理环境Cookie丢失
解决方案(Nginx配置):
nginx复制proxy_cookie_domain api-server.com yourdomain.com;
proxy_cookie_path / /api/;
javascript复制// vite.config.js
export default {
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
rewrite: path => path.replace(/^\/api/, '')
}
}
}
}
javascript复制// Express示例
app.use(cors({
origin: 'http://localhost:5173',
credentials: true
}));
推荐架构:
code复制客户端 → CDN(静态资源) → 网关/Nginx(路由转发) → 微服务集群
关键配置:
OPTIONS方法是HTTP/1.1定义的"询问"方法,专门用于:
这种设计既满足了跨域安全检查的需要,又不会对服务器资源造成实质影响。
场景1:多域名跨域访问
javascript复制const allowedOrigins = ['https://site1.com', 'https://site2.com']
app.use(cors({
origin: (origin, callback) => {
if (allowedOrigins.includes(origin)) {
callback(null, origin)
} else {
callback(new Error('Not allowed'))
}
},
credentials: true
}))
场景2:WebSocket跨域
javascript复制const socket = new WebSocket('wss://api.example.com')
socket.onopen = () => {
socket.send(JSON.stringify({token: 'xxx'}))
}
合理设置Access-Control-Max-Age
避免不必要的预检请求
CDN缓存CORS响应
http复制Access-Control-Max-Age: 3600
Cache-Control: public, max-age=3600
新的安全策略,控制窗口间的引用关系:
http复制Cross-Origin-Opener-Policy: same-origin
影响:
浏览器厂商正在逐步限制第三方Cookie: