ASP.NET Core中JWT令牌解码异常处理及无效令牌提示需求
解决ASP.NET Core中JWT令牌无效时返回友好提示的问题
问题分析
你的代码在JWT令牌被篡改(添加字符)时会触发未捕获的异常,原因是ReadJwtToken方法在令牌无效时会抛出SecurityTokenException,同时代码未处理空引用、令牌格式错误等边界情况,导致程序无法返回友好的无效提示。
修复后的代码
private readonly IHttpContextAccessor _contextAccessor; private readonly ApplicationDbContext dbContext; public UserPolicy(IHttpContextAccessor contextAccessor, ApplicationDbContext dbContext) { _contextAccessor = contextAccessor; this.dbContext = dbContext; } protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, UserPolicyRequirement requirement) { var httpContext = _contextAccessor.HttpContext; if (httpContext == null) { context.Fail(); return; } // 安全获取Action名称,避免空引用 if (!httpContext.Request.RouteValues.TryGetValue("action", out var actionObj) || actionObj == null) { await SetInvalidTokenResponse(httpContext, "无法获取请求的Action信息"); context.Fail(); return; } string actionName = actionObj.ToString(); // 安全获取Controller名称,避免空引用 if (!httpContext.Request.RouteValues.TryGetValue("controller", out var controllerObj) || controllerObj == null) { await SetInvalidTokenResponse(httpContext, "无法获取请求的Controller信息"); context.Fail(); return; } string controllerName = controllerObj.ToString(); var handler = new JwtSecurityTokenHandler(); string token = httpContext.Request.Headers[HeaderNames.Authorization].ToString().Trim(); if (string.IsNullOrEmpty(token)) { await SetInvalidTokenResponse(httpContext, "未提供授权令牌"); context.Fail(); return; } // 验证令牌格式是否符合Bearer规范 if (!token.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) { await SetInvalidTokenResponse(httpContext, "令牌格式错误,需以Bearer开头"); context.Fail(); return; } string jwtToken = token["Bearer ".Length..].Trim(); try { // 读取并验证JWT令牌 var jwtSecurityToken = handler.ReadJwtToken(jwtToken); // 检查令牌中是否存在userId声明 var userIdClaim = jwtSecurityToken.Claims.FirstOrDefault(c => c.Type == "userId"); if (userIdClaim == null) { await SetInvalidTokenResponse(httpContext, "令牌中缺少userId声明"); context.Fail(); return; } string userId = userIdClaim.Value; // 异步验证用户权限,避免线程阻塞 bool hasAccess = await dbContext.userAccessActions.AnyAsync(a => a.UserId == userId && a.ActionName == actionName && a.ControllName == controllerName); if (hasAccess) { context.Succeed(requirement); } else { await SetInvalidTokenResponse(httpContext, "您无权访问该接口"); context.Fail(); } } catch (SecurityTokenException ex) { // 捕获JWT无效相关异常 await SetInvalidTokenResponse(httpContext, $"无效的授权令牌:{ex.Message}"); context.Fail(); } catch (Exception ex) { // 捕获其他未知异常 await SetInvalidTokenResponse(httpContext, $"令牌处理失败:{ex.Message}"); context.Fail(); } } // 统一设置无效令牌的响应内容 private async Task SetInvalidTokenResponse(HttpContext httpContext, string message) { httpContext.Response.StatusCode = StatusCodes.Status401Unauthorized; httpContext.Response.ContentType = "application/json"; var response = new { Status = "Error", Message = message }; await httpContext.Response.WriteAsJsonAsync(response); }
关键优化点
- 异常捕获:专门捕获
SecurityTokenException(JWT无效时抛出)和通用异常,避免程序崩溃,同时返回具体错误信息。 - 边界处理:
- 检查HttpContext是否为空
- 安全获取路由的Action和Controller名称,避免空引用异常
- 验证令牌是否为空、是否符合Bearer格式
- 友好响应:通过统一方法返回JSON格式的错误提示,设置401未授权状态码
- 异步优化:将同步的
Any改为异步AnyAsync,提升性能 - 声明校验:先判断userId声明是否存在,避免
First方法抛出异常
内容的提问来源于stack exchange,提问作者sadegh
相关产品推荐
相关产品推荐

