.NET 6中如何统一添加权限策略对应的日志范围以避免重复编码?
解决方案
针对你的需求,有两种简洁的统一实现方式,无需在每个端点重复编写日志逻辑:
方案一:自定义授权过滤器(IAsyncAuthorizationFilter)
授权过滤器属于MVC管道层级,能精准捕获带有指定策略的请求,且可通过包裹后续流程创建日志范围。
实现步骤:
- 创建自定义过滤器类:
public class ResourceAccessLoggingFilter : IAsyncAuthorizationFilter { private readonly ILogger<ResourceAccessLoggingFilter> _logger; public ResourceAccessLoggingFilter(ILogger<ResourceAccessLoggingFilter> logger) { _logger = logger; } public async Task OnAuthorizationAsync(AuthorizationFilterContext context) { // 检查当前端点是否应用了目标授权策略 var authorizeAttr = context.ActionDescriptor.EndpointMetadata .OfType<AuthorizeAttribute>() .FirstOrDefault(a => a.Policy == "MyResourcePolicyName"); if (authorizeAttr == null) return; // 从Claims获取用户ID(根据你的认证方式调整) var userId = context.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier); // 从路由参数获取资源集ID(根据实际路由规则调整) var resourceSetId = context.RouteData.Values["resourceSetId"]?.ToString(); // 启动日志范围,包裹后续所有请求处理逻辑 using (_logger.BeginScope(new Dictionary<string, object> { ["UserId"] = userId, ["ResourceSetId"] = resourceSetId })) { await Task.CompletedTask; } } }
- 在
Program.cs中注册全局过滤器:
builder.Services.AddControllers(options => { options.Filters.Add<ResourceAccessLoggingFilter>(); });
方案二:自定义中间件检查端点元数据
中间件可在请求流程中提前介入,通过端点元数据判断是否需要添加日志范围,适合覆盖全请求生命周期的记录需求。
实现步骤:
- 创建自定义中间件:
public class ResourceAccessLoggingMiddleware { private readonly RequestDelegate _next; private readonly ILogger<ResourceAccessLoggingMiddleware> _logger; public ResourceAccessLoggingMiddleware(RequestDelegate next, ILogger<ResourceAccessLoggingMiddleware> logger) { _next = next; _logger = logger; } public async Task InvokeAsync(HttpContext context) { var endpoint = context.GetEndpoint(); // 校验端点是否带有目标授权策略 if (endpoint?.Metadata.GetMetadata<AuthorizeAttribute>() is AuthorizeAttribute authorizeAttr && authorizeAttr.Policy == "MyResourcePolicyName") { var userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier); var resourceSetId = context.Request.RouteValues["resourceSetId"]?.ToString(); using (_logger.BeginScope(new Dictionary<string, object> { ["UserId"] = userId, ["ResourceSetId"] = resourceSetId })) { await _next(context); } } else { await _next(context); } } }
- 在
Program.cs中注册中间件(需放在路由中间件之后):
app.UseRouting(); // 注册自定义日志中间件 app.UseMiddleware<ResourceAccessLoggingMiddleware>(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
关键注意事项
- 资源集ID获取:示例从路由参数提取,可根据实际场景调整为从请求体、查询参数等位置获取。
- 用户ID获取:示例使用
ClaimTypes.NameIdentifier,请根据你的认证方案(JWT、Cookie等)替换为对应Claim类型。 - 日志范围作用:
BeginScope会将指定键值对附加到该请求后续的所有日志条目,无需在端点单独重复记录用户和资源信息。
内容的提问来源于stack exchange,提问作者Ben Zuill-Smith
相关产品推荐
相关产品推荐

