.NET Core Web API标注[Authorize]时收到无效Token如何记录日志
JWT无效Token场景日志记录实现方案
针对ASP.NET Core Web API中JwtBearer认证校验失败时提前返回401、无法进入控制器逻辑记录日志的问题,有两种成熟实现方式,可根据业务场景选择:
方案1:绑定JwtBearer内置事件(推荐,仅针对携带无效Token的场景)
JwtBearer认证中间件原生提供了事件钩子,其中OnAuthenticationFailed委托会在请求携带Token但校验全流程任意环节出错时触发,覆盖Token格式非法、签名无效、过期、签发者不匹配等所有无效Token场景,不会改变原有401返回逻辑,侵入性最低。
直接修改现有JwtBearer配置,追加事件处理即可:
builder.Services .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.Configuration = openIdConfig; options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKeyResolver = (string token, SecurityToken securityToken, string kid, TokenValidationParameters validationParameters) => { // 生产环境建议替换为IHttpClientFactory异步拉取,避免.Result同步阻塞导致线程池性能问题 var json = new HttpClient().GetStringAsync(openIdConfig.JwksUri).Result; var jsonWebkeySet = JsonWebKeySet.Create(json); return jsonWebkeySet.GetSigningKeys(); }, ValidateIssuer = true, ValidIssuer = tokenIssuer, ValidateLifetime = true, ValidateAudience = false }; // 追加鉴权失败事件处理 options.Events = new JwtBearerEvents { OnAuthenticationFailed = context => { // 从请求服务容器获取日志实例 var logger = context.HttpContext.RequestServices .GetRequiredService<ILoggerFactory>() .CreateLogger("JwtAuth"); // 记录失败详情,可按需记录请求方法、请求头、用户代理等信息 logger.LogWarning(context.Exception, "JWT校验失败,请求路径:{RequestPath},来源IP:{RemoteIP},错误信息:{ErrorMessage}", context.HttpContext.Request.Path, context.HttpContext.Connection.RemoteIpAddress?.ToString(), context.Exception.Message); return Task.CompletedTask; } }; });
该方案特点:可以拿到具体的校验异常堆栈,方便快速定位是过期、签名错还是签发者不匹配等具体问题,但不会捕获「请求完全未携带Token」导致的401场景。
方案2:自定义中间件全局记录所有401场景
如果需要记录所有返回401的请求(包括未传Token、Token无效等所有未授权场景),可以通过自定义中间件在响应返回阶段拦截状态码做日志记录。
注意中间件注册顺序必须放在认证、授权中间件之前:
var app = builder.Build(); // 先注册鉴权日志中间件 app.Use(async (context, next) => { // 先执行后续中间件逻辑 await next(); // 响应返回前判断状态码是否为401 if (context.Response.StatusCode == StatusCodes.Status401Unauthorized) { var logger = context.RequestServices .GetRequiredService<ILoggerFactory>() .CreateLogger("AuthGlobal"); // 提取请求携带的Token var hasAuthHeader = context.Request.Headers.TryGetValue("Authorization", out var authValues); var rawToken = hasAuthHeader ? authValues.FirstOrDefault()?.Replace("Bearer ", string.Empty) : null; logger.LogWarning( "请求返回401未授权,请求路径:{RequestPath},来源IP:{RemoteIP},携带Token:{TokenPreview}", context.Request.Path, context.Connection.RemoteIpAddress?.ToString(), // Token必须做脱敏处理,禁止明文打印完整Token避免凭证泄露 string.IsNullOrEmpty(rawToken) ? "无" : $"{rawToken[..Math.Min(10, rawToken.Length)]}***" ); } }); // 后续再注册认证、授权、路由中间件 app.UseHttpsRedirection(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.Run();
注意事项
- 所有涉及Token的日志必须做脱敏处理,禁止打印完整Token内容,避免认证凭证泄露
- 原配置中
IssuerSigningKeyResolver使用.Result同步拉取JWKS存在线程阻塞风险,生产环境建议直接使用JwtBearer默认的配置管理器自动拉取、缓存公钥,不需要手动实现公钥拉取逻辑 - 如果需要在鉴权失败时自定义返回格式,也可以在上述两个扩展点中直接向
context.HttpContext.Response写入自定义JSON内容,再调用context.HandleResponse()终止默认返回逻辑即可
内容的提问来源于stack exchange,提问作者chinju
相关产品推荐
相关产品推荐

