ASP.NET Core Web API中JWT令牌用户有效性验证问题
这确实是JWT认证的一个常见痛点——令牌一旦签发,默认情况下只要没过期就会被认可,完全不会去校验用户当前的状态。我之前也帮不少开发者解决过这个问题,用自定义授权策略就能完美搞定,给你一步步拆解:
解决方案:自定义授权策略校验用户实时状态
1. 创建自定义授权需求和处理器
首先我们需要定义一个授权需求标识,然后写对应的处理器,在处理器里去你的用户存储(数据库/缓存等)校验用户是否存在且未被停用。
先创建一个空的需求类(仅作为策略标识,不需要额外参数):
public class ActiveUserRequirement : IAuthorizationRequirement { // 无额外属性,仅用于标记该授权需求 }
然后编写授权处理器,这里需要注入你的用户服务(比如IUserService)来查询用户实时状态,同时注入IHttpContextAccessor获取当前用户的标识:
public class ActiveUserHandler : AuthorizationHandler<ActiveUserRequirement> { private readonly IUserService _userService; private readonly IHttpContextAccessor _httpContextAccessor; public ActiveUserHandler(IUserService userService, IHttpContextAccessor httpContextAccessor) { _userService = userService; _httpContextAccessor = httpContextAccessor; } protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ActiveUserRequirement requirement) { // 从JWT Claims中获取用户唯一标识(这里用NameIdentifier,你也可以用自定义的Claim类型) var userId = _httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier); if (string.IsNullOrEmpty(userId)) { context.Fail(); return; } // 调用用户服务查询用户实时状态 var user = await _userService.GetUserByIdAsync(Guid.Parse(userId)); if (user != null && !user.IsDisabled) { // 用户存在且未停用,授权通过 context.Succeed(requirement); } else { // 用户不存在或已停用,授权失败 context.Fail(); } } }
2. 在Startup.cs中注册策略和处理器
接下来在ConfigureServices方法里,把处理器注册到DI容器,然后添加自定义授权策略:
public void ConfigureServices(IServiceCollection services) { // 保留你原本的JWT认证配置 services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, // 你的其他JWT配置项... }; }); // 注册授权处理器 services.AddScoped<IAuthorizationHandler, ActiveUserHandler>(); // 注册HttpContextAccessor(如果之前没注册过) services.AddHttpContextAccessor(); // 添加自定义授权策略 services.AddAuthorization(options => { options.AddPolicy("ActiveUser", policy => { // 先要求用户已通过JWT认证 policy.RequireAuthenticatedUser(); // 加入我们的自定义用户状态校验需求 policy.AddRequirements(new ActiveUserRequirement()); }); }); // 注册你的用户服务(示例) services.AddScoped<IUserService, UserService>(); services.AddControllers(); }
3. 在接口上使用自定义策略
现在你只需要在需要校验用户实时状态的接口上,用[Authorize(Policy = "ActiveUser")]代替原来的[Authorize]即可:
[ApiController] [Route("api/[controller]")] public class ProtectedController : ControllerBase { // 这个接口会同时校验JWT有效性 + 用户实时状态 [Authorize(Policy = "ActiveUser")] [HttpGet("sensitive-operation")] public IActionResult PerformSensitiveOperation() { return Ok("仅活跃用户可执行此操作"); } // 普通接口仍使用原[Authorize],仅校验JWT有效性 [Authorize] [HttpGet("basic-info")] public IActionResult GetBasicInfo() { return Ok("JWT有效即可访问"); } }
额外优化建议
- 缓存优化:如果高并发场景下每次请求查数据库压力大,可以给用户状态加个短时间缓存(比如5分钟),平衡实时性和性能。
- 错误提示:可以在处理器或自定义中间件中,给授权失败的请求返回更友好的错误信息(比如
403 Forbidden+ "用户已被停用或不存在")。 - Claims补充:如果你的业务允许,可以在令牌刷新时同步更新用户状态相关的Claims,但这只能作为辅助,实时查询数据库才是最可靠的方式。
这样配置后,即使用户的JWT尚未过期,只要他被删除或停用,访问带有ActiveUser策略的接口时就会被拒绝,完美解决你的问题!
内容的提问来源于stack exchange,提问作者David Flury
相关产品推荐
相关产品推荐

