一、为什么需要处理跨域请求?

想象一下这样的场景:你的前端页面运行在https://example.com,但需要从https://api.other.com获取数据。浏览器出于安全考虑,会阻止这种"跨域"请求。这就是我们常说的"同源策略"的限制。

跨域问题就像两家不同的银行之间没有建立信任关系,A银行的卡不能在B银行的ATM机上直接取款。为了让资源能够安全共享,我们需要建立一套规则,这就是CORS(跨域资源共享)策略。

二、OpenResty中CORS的基本配置

OpenResty作为Nginx的增强版,处理CORS非常方便。我们先看一个最简单的配置示例:

# OpenResty配置示例
server {
    listen 80;
    server_name api.example.com;
    
    location / {
        # 允许所有域名访问(生产环境不推荐)
        add_header 'Access-Control-Allow-Origin' '*';
        
        # 允许的HTTP方法
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        
        # 允许的请求头
        add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
        
        # 预检请求缓存时间
        add_header 'Access-Control-Max-Age' 1728000;
        
        # 其他正常配置...
        proxy_pass http://backend;
    }
}

这个配置虽然简单,但有几个关键点需要注意:

  1. Access-Control-Allow-Origin设置为*表示允许所有域名访问,这在开发环境很方便,但生产环境应该限制为特定域名
  2. Access-Control-Allow-Methods定义了允许的HTTP方法
  3. 对于复杂请求(如带自定义头部的请求),浏览器会先发送OPTIONS预检请求

三、生产环境的安全配置

实际项目中,我们需要更严格的配置。下面是一个生产环境推荐的配置:

# OpenResty生产环境CORS配置
server {
    listen 443 ssl;
    server_name api.example.com;
    
    # SSL证书配置
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
    
    location / {
        # 动态获取请求来源,检查是否在白名单中
        set $cors "";
        if ($http_origin ~* (https://example.com|https://www.example.com)) {
            set $cors $http_origin;
        }
        
        # 设置CORS头
        add_header 'Access-Control-Allow-Origin' $cors always;
        add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
        add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With' always;
        add_header 'Access-Control-Allow-Credentials' 'true' always;
        add_header 'Access-Control-Max-Age' 1728000 always;
        
        # 处理OPTIONS预检请求
        if ($request_method = 'OPTIONS') {
            return 204;
        }
        
        # 其他正常配置...
        proxy_pass http://backend;
    }
}

这个配置有几个重要改进:

  1. 使用动态来源检查,只允许白名单中的域名访问
  2. 添加了Access-Control-Allow-Credentials支持带凭证的请求
  3. 使用always参数确保即使错误响应也包含CORS头
  4. 正确处理OPTIONS预检请求

四、高级场景处理

有时候我们会遇到更复杂的需求,比如:

  • 需要根据不同的路由设置不同的CORS策略
  • 需要记录跨域请求日志
  • 需要对特定来源设置特殊权限

下面是一个处理多路由CORS的示例:

# OpenResty多路由CORS配置
server {
    listen 443 ssl;
    server_name api.example.com;
    
    # 公共API - 开放给所有合作伙伴
    location /public/ {
        add_header 'Access-Control-Allow-Origin' '*' always;
        add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS' always;
        
        if ($request_method = 'OPTIONS') {
            return 204;
        }
        
        proxy_pass http://public_backend;
    }
    
    # 私有API - 仅限自己的前端使用
    location /private/ {
        set $cors "";
        if ($http_origin = 'https://myapp.example.com') {
            set $cors $http_origin;
        }
        
        add_header 'Access-Control-Allow-Origin' $cors always;
        add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
        add_header 'Access-Control-Allow-Credentials' 'true' always;
        
        if ($request_method = 'OPTIONS') {
            return 204;
        }
        
        # 额外安全措施:验证来源
        if ($cors = "") {
            return 403;
        }
        
        proxy_pass http://private_backend;
    }
    
    # 管理API - 不允许跨域
    location /admin/ {
        # 不设置任何CORS头
        proxy_pass http://admin_backend;
    }
}

五、常见问题与解决方案

在实际使用中,你可能会遇到这些问题:

  1. 为什么设置了CORS头但还是被浏览器拦截?

    • 检查是否遗漏了OPTIONS方法处理
    • 确保Access-Control-Allow-Headers包含了所有自定义头
    • 如果是带凭证的请求,Access-Control-Allow-Origin不能是*
  2. 如何调试CORS问题?

    • 使用浏览器开发者工具查看网络请求
    • 检查响应头是否包含预期的CORS头
    • 查看服务器日志确认请求是否到达
  3. 性能优化建议

    • 合理设置Access-Control-Max-Age减少预检请求
    • 对于简单请求(GET、HEAD、POST),浏览器不会发送预检请求

六、安全注意事项

配置CORS时,安全是首要考虑因素:

  1. 不要滥用通配符*

    • 只在开发环境使用Access-Control-Allow-Origin: *
    • 生产环境必须限制为特定域名
  2. 小心敏感信息泄露

    • 确保只有必要的API开放跨域访问
    • 对于包含敏感信息的接口,考虑其他安全措施如JWT验证
  3. 防范CSRF攻击

    • 即使配置了CORS,也要实施CSRF防护
    • 对于修改数据的请求,使用CSRF token
  4. 定期审查CORS配置

    • 随着业务发展,定期检查哪些API需要跨域访问
    • 移除不再需要的跨域规则

七、最佳实践总结

经过以上讨论,我们可以总结出OpenResty配置CORS的最佳实践:

  1. 按需开放:只为确实需要跨域的API配置CORS
  2. 最小权限:只允许必要的HTTP方法和头
  3. 动态验证:使用白名单验证请求来源
  4. 安全加固:结合HTTPS、认证等其他安全措施
  5. 日志监控:记录跨域请求便于审计

记住,CORS配置不是一劳永逸的,随着业务发展和安全形势变化,需要定期审查和更新你的配置。

八、完整示例代码

最后,我们来看一个结合了所有最佳实践的完整示例:

# OpenResty CORS最佳实践配置
server {
    listen 443 ssl;
    server_name api.example.com;
    
    # 域名白名单
    set $cors_allowed_origins 'https://example.com https://www.example.com https://partner.example.com';
    
    # SSL配置
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
    
    # 全局CORS设置
    location / {
        # 检查请求来源是否在白名单中
        set $cors_origin "";
        if ($http_origin ~* ($cors_allowed_origins)) {
            set $cors_origin $http_origin;
        }
        
        # 设置CORS头
        add_header 'Access-Control-Allow-Origin' $cors_origin always;
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
        add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Request-ID' always;
        add_header 'Access-Control-Allow-Credentials' 'true' always;
        add_header 'Access-Control-Max-Age' 86400 always;
        
        # 处理OPTIONS请求
        if ($request_method = 'OPTIONS') {
            return 204;
        }
        
        # 来源不在白名单中则拒绝
        if ($cors_origin = "") {
            return 403 "Forbidden - Origin not allowed";
        }
        
        # 其他安全头
        add_header X-Frame-Options DENY;
        add_header X-Content-Type-Options nosniff;
        
        # 代理到后端
        proxy_pass http://backend;
    }
    
    # 健康检查端点 - 不开放跨域
    location /health {
        access_log off;
        proxy_pass http://backend;
    }
}

这个配置示例包含了我们讨论的所有关键点:

  • 动态来源检查
  • 细粒度的CORS控制
  • 安全头设置
  • 清晰的错误提示
  • 特殊端点的例外处理

希望这篇指南能帮助你安全有效地在OpenResty中配置CORS。记住,好的安全配置应该既保护你的资源,又不给合法用户造成不必要的麻烦。