引言

在现代Web开发中,安全始终是一个无法回避的核心议题。当我们构建单页应用(SPA)、微服务架构或前后端分离的项目时,跨域请求几乎无处不在。浏览器作为用户的第一道防线,内置了多种安全机制来保护用户数据不受恶意脚本的侵害。其中,同源策略(Same-Origin Policy, SOP)和跨域资源共享(Cross-Origin Resource Sharing, CORS)是最基石也是最重要的两个安全概念。

本文将从实际开发者的视角,深入剖析同源策略的设计原理、CORS的工作机制,并通过大量实战案例帮助你彻底理解这些概念,避免在日常开发中陷入安全陷阱。

一、同源策略(SOP):浏览器的安全基石

1.1 什么是"源"?

在Web安全中,"源"由三个要素组成:协议(Protocol)、域名(Domain)和端口(Port)。只有当两个URL的这三个要素完全一致时,它们才被认为是"同源"的。

URL与 https://example.com:443/page 比较原因
https://example.com:443/other✅ 同源协议、域名、端口完全一致
http://example.com:80/page❌ 跨源协议不同(https vs http)
https://api.example.com:443/page❌ 跨源域名不同(子域名不同)
https://example.com:8443/page❌ 跨源端口不同(443 vs 8443)
https://example.com/page2✅ 同源路径不同不影响同源判断

1.2 同源策略限制的行为

同源策略并非禁止所有跨源操作,而是精细地限制了以下三类行为:

  • 数据访问:禁止跨源读取DOM、Cookie、LocalStorage、IndexedDB等数据
  • 请求限制:对跨源的XMLHttpRequest和Fetch请求进行限制(尤其是可能产生副作用的写入操作)
  • 资源嵌入:允许跨源的资源嵌入(如<img>、<script>、<link>标签),但限制脚本对嵌入内容的访问

1.3 为什么需要同源策略?

假设没有同源策略,攻击者可以轻易构造以下攻击场景:

  1. 你在浏览器中登录了银行网站 bank.com
  2. 你另一个标签页打开了恶意网站 evil.com
  3. evil.com 的JavaScript直接读取 bank.com 的DOM,获取你的账户余额
  4. evil.com 发起跨源AJAX请求,以你的身份转账

同源策略从根本上阻断了这类攻击,是Web安全的第一道防线。

二、CORS:合法的跨域之道

2.1 CORS的基本原理

CORS(跨域资源共享)是W3C标准,它允许服务器声明哪些外部源可以访问自己的资源。其核心思想是:通过HTTP头部信息,让服务器告诉浏览器"我信任这些源"。

2.2 简单请求 vs 预检请求

浏览器将跨源请求分为两类,采用不同的处理策略:

简单请求(Simple Request)

同时满足以下条件才是简单请求:

  • 请求方法为 GET、HEAD 或 POST
  • Content-Type 为 text/plain、multipart/form-data 或 application/x-www-form-urlencoded
  • 没有设置任何自定义请求头

对于简单请求,浏览器直接发起请求,并附加Origin头部,然后检查响应中的Access-Control-Allow-Origin是否匹配。

预检请求(Preflight Request)

对于不满足简单请求条件的请求(如使用PUT/DELETE方法、Content-Type为application/json、携带自定义头等),浏览器会先发送一个OPTIONS方法的预检请求:

OPTIONS /api/users HTTP/1.1
Host: api.example.com
Origin: https://frontend.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, X-Custom-Header

服务器需要回应以下头部来授权实际请求:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://frontend.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, X-Custom-Header
Access-Control-Max-Age: 86400
Access-Control-Allow-Credentials: true

2.3 关键CORS响应头详解

响应头说明注意事项
Access-Control-Allow-Origin允许访问的源值为*或具体域名,与Credentials互斥时不能用*
Access-Control-Allow-Methods允许的HTTP方法预检请求的响应
Access-Control-Allow-Headers允许的请求头预检请求的响应
Access-Control-Allow-Credentials是否允许携带凭证值为true时,Allow-Origin不能为*
Access-Control-Max-Age预检请求缓存时间单位为秒,减少重复预检的开销
Access-Control-Expose-Headers允许前端访问的响应头默认只有Cache-Control等安全头可见

三、实战:在主流框架中配置CORS

3.1 Node.js Express配置

const express = require('express');
const cors = require('cors');
const app = express();

// 方式1:允许所有源(开发环境)
app.use(cors());

// 方式2:精细控制(生产环境推荐)
app.use(cors({
  origin: ['https://www.example.com', 'https://app.example.com'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true,
  maxAge: 86400
}));

// 方式3:动态白名单验证
const allowedOrigins = new Set([
  'https://www.example.com',
  'https://app.example.com'
]);

app.use(cors({
  origin: (origin, callback) => {
    // 允许无origin的请求(如移动端、Postman)
    if (!origin || allowedOrigins.has(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  },
  credentials: true
}));

3.2 Spring Boot配置

@Configuration
@EnableWebMvc
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
            .allowedOrigins("https://www.example.com")
            .allowedMethods("GET", "POST", "PUT", "DELETE")
            .allowedHeaders("*")
            .allowCredentials(true)
            .maxAge(3600);
    }
}

// 或者使用注解方式
@RestController
@CrossOrigin(
    origins = "https://www.example.com",
    methods = {RequestMethod.GET, RequestMethod.POST},
    allowedHeaders = "*",
    allowCredentials = "true"
)
@RequestMapping("/api")
public class ApiController {
    // ...
}

3.3 Nginx反向代理

location /api/ {
    # 处理预检请求
    if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' '$http_origin';
        add_header 'Access-Control-Allow-Methods' 'GET,POST,PUT,DELETE,OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'Content-Type,Authorization';
        add_header 'Access-Control-Allow-Credentials' 'true';
        add_header 'Access-Control-Max-Age' 86400;
        add_header 'Content-Length' 0;
        return 204;
    }

    add_header 'Access-Control-Allow-Origin' '$http_origin' always;
    add_header 'Access-Control-Allow-Credentials' 'true' always;

    proxy_pass http://backend:8080;
}

四、常见CORS错误与排错指南

4.1 错误信息速查

浏览器控制台错误常见原因解决方案
No 'Access-Control-Allow-Origin' header服务器未配置CORS或配置错误检查服务器端CORS配置是否正确返回A头部
The value of the 'Access-Control-Allow-Origin' header must not be the wildcard '*' when credentials flag is set带credentials时使用了*通配符使用具体域名白名单,不要使用*
Method PUT is not allowed预检请求未允许PUT方法在Allow-Methods中添加PUT
Request header field X-Custom is not allowed自定义头未在Allow-Headers中声明在Allow-Headers中添加对应头部
Preflight response is not successful预检请求返回非2xx状态码确保OPTIONS请求返回200/204

4.2 排错技巧

  • 使用Network面板:在Chrome DevTools中勾选"Preserve log",观察OPTIONS预检请求的请求头和响应头
  • curl模拟预检:使用curl -X OPTIONS -H "Origin: https://frontend.com" -H "Access-Control-Request-Method: PUT" -I https://api.com/endpoint快速测试
  • 检查代理层:确认Nginx/CDN层是否会过滤或覆盖CORS头部
  • 日志追踪:在服务端添加CORS相关的日志输出,确认中间件正确处理了请求

五、安全最佳实践

5.1 严格的白名单策略

永远不要使用Access-Control-Allow-Origin: *来信任所有源,尤其是在需要携带Cookie或Authorization头部的场景中。应该维护一个严格的域名白名单。

5.2 验证Origin而非反射

不要简单地将请求的Origin反射到响应头中,这样等同于允许所有来源。应该通过白名单验证:

// ❌ 危险:反射Origin等于允许所有来源
response.headers['Access-Control-Allow-Origin'] = request.headers['Origin']

// ✅ 安全:白名单验证
if (allowedOrigins.includes(request.headers['Origin'])) {
    response.headers['Access-Control-Allow-Origin'] = request.headers['Origin']
}

5.3 配合其他安全措施

  • 使用CSRF Token防止跨站请求伪造
  • 设置SameSite=Strict/Lax的Cookie属性
  • 实施CSP(内容安全策略)限制脚本执行
  • 敏感操作增加二次验证
  • 实施Rate Limiting防止暴力攻击

六、其他跨域方案对比

方案原理优点缺点
CORSHTTP头部声明权限标准方案,支持所有请求类型需要服务端支持,老浏览器不兼容
JSONP利用script标签无跨源限制兼容老浏览器仅GET请求,存在XSS风险,已被废弃
反向代理前后端同域,Nginx转发无需修改后端,对前端透明增加架构复杂度,运维成本
WebSocket独立的通信协议,不受SOP限制全双工实时通信协议不同,需专门处理
postMessage跨窗口/iframe消息传递安全的跨源通信仅限窗口间通信,需双方配合

总结

同源策略和CORS是Web安全体系中最基础也是最重要的部分。理解它们的原理,不仅能帮助我们正确配置跨域请求,更能让我们从根本上理解浏览器如何保护用户数据。在实际开发中,始终坚持"最小权限原则"——只开放必要的源、方法和头部,配合CSRF防护、CSP等多层安全策略,才能构建真正安全的Web应用。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿
网站二维码

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部