.NET Core项目中如何隔离内外API的Swagger文档并做权限控制
针对你的Umbraco + .NET Core项目场景,以下是几种可行的实现方案,适配你提到的不同需求:
方案一:独立配置公共/内部Swagger UI + 端点授权
这种方案将公共和内部Swagger文档完全分离,通过中间件严格控制内部文档的访问权限,适合需要强隔离的场景。
步骤1:配置授权策略
在服务注册阶段(Program.cs或Startup.cs)添加授权规则,根据你的认证方式定义内部用户的访问权限:
services.AddAuthorization(options => { options.AddPolicy("InternalApiAccess", policy => { // 示例:要求用户拥有InternalUser角色 policy.RequireRole("InternalUser"); // 若使用Umbraco后台身份,可替换为Umbraco内置角色 // policy.RequireClaim(ClaimTypes.Role, Umbraco.Cms.Core.Security.Constants.Security.AdminRole); }); });
步骤2:配置双Swagger UI端点
在管道配置阶段,分别为公共和内部文档设置独立的UI路径,并为内部路径添加授权校验:
// 公共Swagger UI:无需授权,直接访问 app.UseSwaggerUI(c => { c.RoutePrefix = "swagger/public"; c.SwaggerEndpoint("/swagger/public/swagger.json", "Public Facing API v1"); c.SupportedSubmitMethods(new SubmitMethod[] { SubmitMethod.Get, SubmitMethod.Head }); }); // 内部Swagger UI:仅授权用户可访问 app.MapWhen(context => context.Request.Path.StartsWithSegments("/swagger/internal"), internalApp => { internalApp.UseAuthorization(); internalApp.UseSwaggerUI(c => { c.RoutePrefix = "swagger/internal"; c.SwaggerEndpoint("/swagger/internal/swagger.json", "Internal API v1"); c.SupportedSubmitMethods(new SubmitMethod[] { SubmitMethod.Get, SubmitMethod.Head }); }); });
步骤3:保护内部Swagger JSON端点
直接访问/swagger/internal/swagger.json也能获取内部API信息,需单独对该路径添加授权:
app.MapGet("/swagger/internal/swagger.json", async context => { // 验证用户权限 if (!await context.AuthorizeAsync("InternalApiAccess")) { context.Response.StatusCode = StatusCodes.Status401Unauthorized; return; } // 转发请求到Swagger中间件生成JSON await app.ApplicationServices.GetRequiredService<SwaggerMiddleware>().Invoke(context); }).RequireAuthorization("InternalApiAccess");
方案二:单文档动态过滤API内容
如果希望统一Swagger入口,根据用户身份动态显示公共/内部API,可使用Swashbuckle的DocumentFilter实现。
步骤1:创建文档过滤逻辑
实现IDocumentFilter接口,根据当前用户权限移除未授权的API操作:
public class InternalApiFilter : IDocumentFilter { private readonly IHttpContextAccessor _httpContextAccessor; public InternalApiFilter(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) { var httpContext = _httpContextAccessor.HttpContext; if (httpContext == null) return; // 判断用户是否有权限查看内部API var canViewInternal = httpContext.User.Identity.IsAuthenticated && httpContext.User.IsInRole("InternalUser"); if (!canViewInternal) { // 移除所有标记为「Internal」的API路径 var internalPaths = swaggerDoc.Paths .Where(p => p.Value.Operations.Any(o => o.Value.Tags.Any(t => t.Name == "Internal"))) .ToList(); foreach (var path in internalPaths) { swaggerDoc.Paths.Remove(path.Key); } } } }
步骤2:注册过滤器并标记内部API
在AddSwaggerGen中注册过滤器,同时在内部API控制器/方法上添加[Tags("Internal")]标记:
services.AddSwaggerGen(options => { options.SwaggerDoc("v1", new OpenApiInfo { Title = "Combined API", Version = "v1", }); // 注册文档过滤器 options.DocumentFilter<InternalApiFilter>(); }); // 内部API控制器示例 [ApiController] [Route("api/internal")] [Tags("Internal")] public class InternalController : ControllerBase { // 内部API方法... }
步骤3:配置统一Swagger UI
无需拆分UI,直接配置单入口即可,未授权用户访问时只会看到公共API:
app.UseSwaggerUI(c => { c.RoutePrefix = "swagger"; c.SwaggerEndpoint("/swagger/v1/swagger.json", "Combined API v1"); c.SupportedSubmitMethods(new SubmitMethod[] { SubmitMethod.Get, SubmitMethod.Head }); });
方案三:嵌入Swagger到Umbraco后台(复用Umbraco权限)
如果内部用户已使用Umbraco后台,可将内部Swagger文档嵌入Umbraco后台视图,直接复用Umbraco的权限体系。
步骤1:创建Umbraco后台分部视图
在Views/Partials目录下创建InternalSwagger.cshtml,嵌入Swagger UI资源:
<div id="swagger-ui-container"></div> <script src="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js"></script> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css" /> <script> window.onload = () => { SwaggerUIBundle({ url: '/swagger/internal/swagger.json', dom_id: '#swagger-ui-container', presets: [SwaggerUIBundle.presets.apis], supportedSubmitMethods: ['get', 'head'] }); }; </script>
步骤2:创建授权的Surface控制器
创建控制器返回该视图,并添加Umbraco权限校验:
[Authorize(Policy = Umbraco.Cms.Core.Security.Constants.Security.AdminRole)] public class InternalSwaggerController : SurfaceController { public IActionResult Index() => PartialView("~/Views/Partials/InternalSwagger.cshtml"); }
步骤3:配置Umbraco后台菜单项
通过Composer添加后台菜单项,仅授权用户可见:
public class SwaggerDashboardComposer : IComposer { public void Compose(IUmbracoBuilder builder) { builder.Dashboards().AddDashboard<InternalSwaggerDashboard>(); } } [Dashboard( Alias = "internalApiDocs", Name = "内部API文档", Sections = new[] { Umbraco.Cms.Core.Security.Constants.Sections.Settings }, AccessRules = new[] { new AccessRule { Type = AccessRuleType.Role, Value = Umbraco.Cms.Core.Security.Constants.Security.AdminRole } })] public class InternalSwaggerDashboard : IDashboard { public IEnumerable<IComposedDashboard> GetDashboards() { return new List<IComposedDashboard> { new ComposedDashboard { ViewPath = "/Views/Partials/InternalSwagger.cshtml" } }; } }
步骤4:保护内部Swagger JSON端点
确保只有Umbraco授权用户能访问内部API的JSON文档:
app.MapGet("/swagger/internal/swagger.json", async context => { var umbracoContext = context.RequestServices.GetRequiredService<IUmbracoContextAccessor>().GetRequiredUmbracoContext(); var currentUser = umbracoContext.Security.CurrentUser; if (currentUser == null || !currentUser.IsInRole(Umbraco.Cms.Core.Security.Constants.Security.AdminRole)) { context.Response.StatusCode = StatusCodes.Status401Unauthorized; return; } await app.ApplicationServices.GetRequiredService<SwaggerMiddleware>().Invoke(context); }).RequireAuthorization(Umbraco.Cms.Core.Security.Constants.Security.AdminRole);
方案选择建议
- 方案一:优先选择,权限隔离清晰,维护简单,适合大多数场景。
- 方案二:适合希望统一Swagger入口,且内部API无需严格隐藏JSON文档的场景。
- 方案三:适合内部用户已使用Umbraco后台,无需额外维护独立认证体系的场景。
内容的提问来源于stack exchange,提问作者glenatron

