基于Microsoft Identity的Swagger接口按角色隐藏失效问题
问题原因与解决方案
你当前的代码虽然进入了!isUserInRole分支,但只是修改了OpenApiOperation的属性(清除标签、摘要等),并没有从Swagger文档的核心路径集合中移除对应的接口条目,所以Swagger UI依然会显示该接口。要彻底隐藏接口,需要直接操作Swagger文档的Paths集合,改用IDocumentFilter实现,而不是IOperationFilter。
步骤1:修改过滤器实现为IDocumentFilter
替换原HideEndpointsBasedOnUserFilter类的代码,直接操作整个Swagger文档来移除不需要的接口:
using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; using System.Collections.Generic; using System.Linq; using System.Reflection; public class HideEndpointsBasedOnUserFilter : IDocumentFilter { private readonly IHttpContextAccessor _httpContextAccessor; public HideEndpointsBasedOnUserFilter(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) { var user = _httpContextAccessor.HttpContext?.User; if (user == null || !user.Identity.IsAuthenticated) { return; } // 收集需要移除的路径和操作 var pathsToRemove = new List<KeyValuePair<string, OpenApiPathItem>>(); foreach (var pathEntry in swaggerDoc.Paths) { var operationsToRemove = new List<OperationType>(); foreach (var operationEntry in pathEntry.Value.Operations) { // 匹配当前操作对应的控制器Action var apiDesc = context.ApiDescriptions.FirstOrDefault(api => api.RelativePath == pathEntry.Key.Replace("/cabinetGatewaySvc/", "") && api.HttpMethod.Equals(operationEntry.Key.ToString(), System.StringComparison.OrdinalIgnoreCase)); if (apiDesc?.ActionDescriptor is not ControllerActionDescriptor actionDescriptor) continue; // 检查是否存在SwaggerHideAttribute var hideAttribute = actionDescriptor.MethodInfo.GetCustomAttribute<SwaggerHideAttribute>(); if (hideAttribute == null) continue; var requiredRole = hideAttribute.RequiredRole; var isUserInRole = user.IsInRole(requiredRole); Console.WriteLine($"Required role: {requiredRole}, Is user in role: {isUserInRole}"); if (!isUserInRole) { operationsToRemove.Add(operationEntry.Key); } } // 移除当前路径下的目标操作 foreach (var opType in operationsToRemove) { pathEntry.Value.Operations.Remove(opType); } // 如果路径下没有剩余操作,移除整个路径 if (!pathEntry.Value.Operations.Any()) { pathsToRemove.Add(pathEntry); } } // 移除空路径 foreach (var pathEntry in pathsToRemove) { swaggerDoc.Paths.Remove(pathEntry.Key); } } }
步骤2:更新Swagger注册配置
在Program.cs的AddSwaggerGen中,将原来的OperationFilter替换为DocumentFilter:
builder.Services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "Cabinet gateway", Version = "v1", Description = "Cabinet gateway documentation." }); // 替换为DocumentFilter c.DocumentFilter<HideEndpointsBasedOnUserFilter>(); // ... 其他原有配置(XML注释、安全定义等)保持不变 ... });
步骤3:禁用Swagger文档缓存
Swagger默认会缓存生成的文档,导致用户登录后看不到实时的接口权限变化,需要在UseSwagger中添加缓存控制:
app.UseSwagger(c => { c.RouteTemplate = "cabinetGatewaySvc/swagger/{documentname}/swagger.json"; // 添加缓存控制头,禁止浏览器缓存Swagger文档 c.PreSerializeFilters.Add((swaggerDoc, httpReq) => { httpReq.HttpContext.Response.Headers.Append("Cache-Control", "no-cache, no-store, must-revalidate"); httpReq.HttpContext.Response.Headers.Append("Pragma", "no-cache"); httpReq.HttpContext.Response.Headers.Append("Expires", "0"); }); });
额外修复:原代码中的语法错误
原IOperationFilter实现中,获取SwaggerHideAttribute的代码有语法错误,缺少闭合的>:
// 错误写法 var hideAttribute = context.MethodInfo.GetCustomAttribute<SwaggerHideAttribute (); // 正确写法 var hideAttribute = context.MethodInfo.GetCustomAttribute<SwaggerHideAttribute>();
完成以上修改后,没有对应角色的登录用户将不会在Swagger UI中看到标记了[SwaggerHide("Admin")]的接口。
内容的提问来源于stack exchange,提问作者Zordanian
相关产品推荐
相关产品推荐

