0

Nginx CORS与JWT Token认证配置方案:解决跨域与身份验证的完整实战

2026.05.24 | youres | 23次围观

前言

在现代Web开发中,前后端分离架构已经成为主流。前端应用通常部署在example.com,后端API部署在api.example.com,这种跨域场景必然遇到CORS(跨域资源共享)问题。同时,API身份验证往往使用JWT(JSON Web Token)方案。当CORS与JWT同时配置时,很多开发者会遇到跨域请求中Token丢失、预检请求失败等棘手问题。本文将手把手教你如何在Nginx中正确配置CORS与JWT Token认证,让跨域身份验证畅通无阻。

一、CORS与JWT基础概念

什么是CORS

CORS(Cross-Origin Resource Sharing,跨域资源共享)是一种基于HTTP头的安全机制,允许服务器声明哪些源站有权限访问哪些资源。浏览器会针对跨域请求自动发起CORS预检(preflight),发送一个OPTIONS请求,询问服务器是否允许实际请求。

什么是JWT Token

JWT(JSON Web Token)是一种紧凑的、自包含的Token格式,用于在各方之间安全地传输信息。一个JWT Token由三部分组成:Header(头部)、Payload(载荷)、Signature(签名)。前端在请求头中携带Authorization: Bearer <token>来完成后端身份验证。

为什么CORS与JWT会冲突

当JWT Token放在Authorization头中时,这属于"非简单请求头",浏览器会先发OPTIONS预检请求。如果Nginx的CORS配置不当,预检请求会被拒绝,或者实际请求的Authorization头被浏览器拦截,导致Token无法传递到后端。

二、Nginx CORS基础配置回顾

在配置JWT认证之前,先确保CORS基础配置正确。以下是最常用的Nginx CORS配置模板:

location /api/ {
    # CORS基础头
    add_header 'Access-Control-Allow-Origin' '$http_origin' always;
    add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
    add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Accept' always;
    add_header 'Access-Control-Allow-Credentials' 'true' always;
    add_header 'Access-Control-Max-Age' '86400' always;

    # 处理OPTIONS预检请求
    if ($request_method = 'OPTIONS') {
        return 204;
    }

    # 反向代理到后端
    proxy_pass http://backend;
}

关键点:Access-Control-Allow-Headers必须显式包含Authorization,否则浏览器不允许前端在请求头中携带JWT Token。

三、JWT Token在Nginx中的验证方式

Nginx本身不解析JWT Token内容(除非使用OpenResty/Lua),常见的JWT处理方案有三种:

方案一:Nginx仅做CORS,JWT验证交给后端

这是最简单、最常用的方案。Nginx负责处理CORS跨域,JWT的签发和验证全部由后端应用(如Node.js、Java、PHP)完成。Nginx只需要正确传递Authorization请求头即可。

方案二:使用OpenResty+Lua在Nginx层验证JWT

如果希望在Nginx层就拦截无效Token,可以使用OpenResty的lua-resty-jwt库。这种方式能减少无效请求到达后端的流量,适合高并发场景。

方案三:使用Nginx Auth Request模块

Nginx的auth_request模块可以将Token验证委托给一个独立的验证子请求(转发到验证服务),验证通过才允许访问实际资源。

四、完整实战:Nginx CORS + JWT 配置方案

下面以方案一(Nginx做CORS,后端验证JWT)为例,给出生产环境可直接使用的完整配置。

4.1 基础CORS + 反向代理配置

server {
    listen 443 ssl http2;
    server_name api.example.com;

    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    location /api/ {
        # === CORS配置开始 ===
        set $cors_origin 'https://www.frontend.com';
        if ($http_origin ~ '^https://(www\.frontend\.com|admin\.frontend\.com)$') {
            set $cors_origin $http_origin;
        }
        add_header 'Access-Control-Allow-Origin' $cors_origin always;
        
        add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS, PATCH' always;
        add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Accept, X-Requested-With' always;
        add_header 'Access-Control-Allow-Credentials' 'true' always;
        add_header 'Access-Control-Expose-Headers' 'Authorization' always;
        add_header 'Access-Control-Max-Age' '86400' always;
        # === CORS配置结束 ===

        # 处理OPTIONS预检请求
        if ($request_method = 'OPTIONS') {
            return 204;
        }

        # 将Authorization头传递给后端(关键!)
        proxy_set_header Authorization $http_authorization;
        proxy_pass_header Authorization;

        # 其他标准代理头
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_pass http://127.0.0.1:8080;
    }
}

4.2 配置逐行解析

配置项作用
Access-Control-Allow-Origin允许跨域请求的源。携Cookie时不能用通配符*,必须明确指定域名。
Access-Control-Allow-Headers明确允许Authorization头,前端才能成功携带JWT Token。
Access-Control-Allow-Credentials设为true时,前端请求需要设置withCredentials: true,Cookie和HTTP认证信息才会被发送。
Access-Control-Expose-Headers允许前端JavaScript访问的响应头。如果后端在响应头中返回新的Token,需要在此暴露。
OPTIONS返回204预检请求不需要转发到后端,Nginx直接返回204 No Content,减轻后端压力。
proxy_set_header Authorization关键中的关键:将前端传来的Authorization头原样传递给后端,否则后端拿不到JWT Token。

五、前端如何正确携带JWT Token

后端和Nginx配置好后,前端也需要正确配置才能携带Token。以fetch为例:

// 正确示例:携带JWT Token的跨域请求
fetch('https://api.example.com/api/user/profile', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer ' + localStorage.getItem('jwt_token'),
    'Content-Type': 'application/json'
  },
  credentials: 'include'  // 如果需要携带Cookie,必须设置此项
})
.then(res => res.json())
.then(data => console.log(data));

注意:如果Nginx配置了Access-Control-Allow-Credentials: true,前端的credentials必须设为'include',否则Cookie不会被发送。

六、使用OpenResty在Nginx层验证JWT(进阶方案)

如果你使用OpenResty(Nginx+Lua),可以在Nginx层直接验证JWT签名,无效Token直接拒绝,不转发到后端。

location /api/ {
    access_by_lua_block {
        local jwt = require "resty.jwt"
        local token = ngx.var.http_authorization
        if not token then
            ngx.status = 401
            ngx.header["Access-Control-Allow-Origin"] = ngx.var.http_origin
            ngx.header["Access-Control-Allow-Credentials"] = "true"
            ngx.say('{"error":"missing token"}')
            return ngx.exit(401)
        end
        
        -- 去掉 "Bearer " 前缀
        local token_str = token:sub(8)
        local verified = jwt:verify("your-secret-key", token_str)
        
        if not verified.verified then
            ngx.status = 401
            ngx.header["Access-Control-Allow-Origin"] = ngx.var.http_origin
            ngx.header["Access-Control-Allow-Credentials"] = "true"
            ngx.say('{"error":"invalid token"}')
            return ngx.exit(401)
        end
        
        -- Token有效,将payload传递给后端
        ngx.req.set_header("X-User-Id", verified.payload.sub)
    }

    proxy_pass http://backend;
}

七、常见问题与解决方法

问题1:前端控制台报错"Authorization header is not allowed"

原因:Access-Control-Allow-Headers中没有包含Authorization

解决:在Nginx配置中添加add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type' always;

问题2:OPTIONS预检请求返回401/403

原因:预检请求被后端的JWT验证中间件拦截了。OPTIONS请求不带Token,但后端中间件对所有请求都验证Token。

解决:后端中间件对OPTIONS请求跳过验证;或者像本文配置一样,在Nginx层直接返回204,不转发OPTIONS到后端。

问题3:跨域请求能成功,但Response Header里拿不到Authorization

原因:没有配置Access-Control-Expose-Headers。浏览器默认只允许前端访问简单响应头(Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma)。

解决:添加add_header 'Access-Control-Expose-Headers' 'Authorization' always;

问题4:设置了withCredentials但还是无法携带Cookie

原因:Access-Control-Allow-Origin使用了通配符*。当请求需要携带凭证(Cookie、HTTP认证)时,CORS规范不允许使用*

解决:Access-Control-Allow-Origin改为明确的域名,如https://www.frontend.com

八、安全最佳实践

  • 不要用*作为Allow-Origin:生产环境务必明确指定允许的域名,避免任意网站都能跨域访问你的API。
  • JWT Secret要足够复杂:使用至少32位的随机字符串作为JWT签名密钥,防止Token被伪造。
  • 设置JWT过期时间:Access Token有效期建议设为15-30分钟,使用Refresh Token机制续期。
  • HTTPS everywhere:JWT Token在传输过程中必须走HTTPS,防止被中间人劫持。
  • 避免在Token中存储敏感信息:JWT的Payload是Base64编码(非加密),任何人都可以解码查看,不要放密码、身份证号等敏感数据。
  • 使用HttpOnly+Secure Cookie存储Token:如果Token通过Cookie传递,务必设置HttpOnly(防XSS)和Secure(仅HTTPS)属性。

九、相关文章推荐

如果你对Nginx CORS配置的更多细节感兴趣,可以阅读以下相关文章:

十、总结

Nginx配置CORS与JWT Token认证的核心要点可以归纳为三句话:

  1. Access-Control-Allow-Headers必须包含Authorization,否则前端无法携带Token;
  2. OPTIONS预检请求要在Nginx层直接处理(返回204),不要转发到后端;
  3. 使用proxy_set_header Authorization $http_authorization;将Token传递给后端,这一步最容易被遗漏。

按照本文的配置模板操作,你可以快速搭建起一个同时支持CORS跨域和JWT身份验证的Nginx反向代理层。如果在配置过程中遇到其他问题,欢迎在评论区留言讨论。

版权声明

本文仅代表个人观点。
本文系AI辅助作者原创,未经许可,转载请保留原文链接。

发表评论
883文章数 0评论数
作者其它文章