近日,大量 .NET Core 开发者反映,在使用 Swagger(Swashbuckle)为 API 生成交互式文档时,无论是否携带有效的身份验证令牌,所有请求均被拒绝并返回 401 Unauthorized 错误。这一现象迅速在 GitHub Issues、Stack Overflow 和中文技术社区引发讨论,被戏称为“Swagger 401 幽灵漏洞”。经过多位资深工程师排查,问题根源已锁定:Swagger UI 的 CORS 与认证中间件配置冲突,以及 未正确排除 Swagger 端点免于认证。
问题重现:一个“正常”的 API 为何在 Swagger 上集体罢工?
杭州某互联网公司后端团队在开发基于 .NET 8 的微服务时,按照官方文档集成了 Swashbuckle.AspNetCore 并启用了 JWT 认证。在 Postman 中,所有 API 均能正常响应:未提供令牌时返回 401,提供有效令牌则返回 200。然而,一旦通过 Swagger UI(通常在 /swagger 或 /swagger/index.html 端点)发起请求,无论是否点击“Authorize”按钮并输入令牌,所有请求一律返回 401。
“最诡异的是,我们明明在 Swagger 的配置中注册了安全定义,并且点击了锁图标输入了 Bearer Token,但实际发起的 HTTP 请求却根本没有携带 Authorization 头。”该团队技术负责人陈工表示。经过 Wireshark 抓包,他们发现 Swagger UI 发起的请求确实没有携带认证头——但更令人困惑的是,即使手动在浏览器的控制台中使用 fetch 添加 Authorization: Bearer xxx,依然返回 401。
根因分析:中间件顺序与 Swagger 的“盲区”
.NET Core 的请求管道(Middleware Pipeline)按照注册顺序依次执行。典型的配置如下:
app.UseAuthentication();
app.UseAuthorization();
app.UseSwagger();
app.UseSwaggerUI();
这里隐藏着一个陷阱:UseAuthentication 和 UseAuthorization 早于 UseSwagger 执行。如果开发者在全局控制器或路由上使用 [Authorize] 特性(如 [Authorize] 装饰在 Controller 类上),那么所有请求(包括对 Swagger 静态资源的请求)都会经过认证中间件检查。即使 Swagger UI 页面本身是匿名可访问的,但 Swagger 向后端发起请求时,这些请求也受全局认证策略管辖。
更关键的是,许多开发者在配置 Swagger 安全定义时,错误地将 AddSecurityDefinition 和 AddSecurityRequirement 放在了 Swagger 文档生成逻辑中,却没有在 Swagger UI 的 JavaScript 层面正确传递令牌。这导致 Swagger UI 发送的请求虽然携带了 Authorization 头(通过“Authorize”按钮输入),但后端中间件因为 CORS 预检请求(OPTIONS)或自定义过滤器而拒绝。
“幽灵”的三种常见形态
经过社区分类,造成“始终返回 401”的主要场景有以下三类:
场景一:全局认证拦截了 Swagger 端点
表现:在 Startup.cs 或 Program.cs 中,开发者在 UseAuthorization 之前没有添加 AllowAnonymous 到 Swagger 相关路径。
解决:在中间件管道中,将 app.UseSwagger() 和 app.UseSwaggerUI() 提前到 UseAuthentication 之前,或者使用 When 扩展方法来显式排除 /swagger 路径。更推荐的做法是在 UseAuthorization 中间件中配置权限策略,允许匿名访问 Swagger 端点。
app.UseWhen(context => !context.Request.Path.StartsWithSegments("/swagger"), appBuilder =>
{
appBuilder.UseAuthentication();
appBuilder.UseAuthorization();
});
app.UseSwagger();
app.UseSwaggerUI();
场景二:JWT 验证方案中缺少对 OPTIONS 请求的处理
表现:浏览器发送 CORS 预检请求(OPTIONS)到 API 端点,而服务器未正确返回 204,导致实际请求被阻止。
解决:确保 AddJwtBearer 中设置了 Events 来提前返回 OnChallenge 或 OnForbidden,并确认 CORS 中间件已正确配置且位于认证中间件之前。
场景三:Swagger UI 未正确发送 Authorization 头
表现:在 Swagger 页面点击“Authorize”后,令牌未保存到 localStorage,或保存后未在每次请求中附加。
解决:检查 AddSecurityDefinition 中的 In = ParameterLocation.Header 以及 Scheme = "Bearer" 是否正确。同时确保 Swagger UI 版本与 API 版本兼容,某些旧版 Swashbuckle 存在已知的 DTO 序列化问题。
官方回应与社区最佳实践
微软官方在 .NET 9 预览版中已计划优化 Swagger 集成,但当前版本仍需开发者手动处理。社区总结出一套“三保险”方案:
- 分离认证策略:对 Swagger 端点使用显式
[AllowAnonymous](如果使用 Controller 路由),或通过中间件条件排除。 - 严格中间件顺序:CORS 中间件放在最前,Swagger 中间件放在认证之前。
- 验证调试:使用浏览器的开发者工具查看实际发起请求的 Headers,确认
Authorization字段存在且值以Bearer开头。
对开发者的启示
此次“Swagger 401”事件虽然被社区迅速解决,但暴露出 .NET 生态在 API 文档与安全机制集成上仍存在设计瑕疵。对于正在迁移或新构建 .NET Core API 的团队,建议在项目初期就将 Swagger 的安全配置纳入测试用例,并建立中间件顺序的代码审查规范。毕竟,一个“始终返回 401”的文档页面,不仅让前端对接团队抓狂,更可能成为生产环境的安全隐患——当所有人都绕开 Swagger 测试时,真正的认证逻辑反而可能被忽视。
截至发稿,该问题在 GitHub 上的核心议题仍处于开放状态,但社区提供的多种变通方案已帮助数千名开发者走出了“401 迷宫”。对于尚未升级至 .NET 9 的开发者,不妨对照上述场景逐一排查,或许那个“幽灵”就在你的 Startup 管道里。