ASP.NET中基于JWT的userId从数据库动态加载角色实现问询
解决方案
一、动态加载用户角色与额外信息(仅存userId在JWT)
你需要用到IClaimsTransformation接口,它会在JWT验证通过后自动触发,用来扩展或转换用户的Claims,正好适合从数据库拉取角色、用户详情等信息并添加到当前用户身份中。
1. 实现自定义Claims转换器
创建一个类实现IClaimsTransformation,注入你的数据库上下文(比如AppDbContext):
public class CustomClaimsTransformer : IClaimsTransformation { private readonly AppDbContext _dbContext; public CustomClaimsTransformer(AppDbContext dbContext) { _dbContext = dbContext; } public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal) { // 从现有Claims中获取userId(和你签发JWT时存的ClaimKey一致,比如"userId"或JwtRegisteredClaimNames.Sub) var userIdClaim = principal.FindFirst("userId") ?? principal.FindFirst(JwtRegisteredClaimNames.Sub); if (userIdClaim == null || !Guid.TryParse(userIdClaim.Value, out var userId)) { return principal; } // 从数据库查询用户及关联角色 var user = await _dbContext.Users .Include(u => u.Roles) .FirstOrDefaultAsync(u => u.Id == userId); if (user == null) { return principal; } // 基于原有身份创建新的ClaimsIdentity,添加扩展信息 var identity = new ClaimsIdentity(principal.Identity); foreach (var role in user.Roles) { identity.AddClaim(new Claim(ClaimTypes.Role, role.Name)); } identity.AddClaim(new Claim(ClaimTypes.Name, user.FullName)); identity.AddClaim(new Claim("Email", user.Email)); return new ClaimsPrincipal(identity); } }
2. 注册转换器服务
在ConfigureServices中添加以下代码:
// 注册数据库上下文(若未注册) services.AddDbContext<AppDbContext>(options => options.UseSqlServer(_configuration.GetConnectionString("DefaultConnection"))); // 注册自定义Claims转换器 services.AddScoped<IClaimsTransformation, CustomClaimsTransformer>();
二、验证Token签发时间是否晚于密码修改时间
在JWT验证通过后,通过AddJwtBearer的Events自定义逻辑,对比Token签发时间与用户密码最后修改时间,拒绝过期的旧Token。
1. 配置JWT验证事件
修改你现有的AddJwtBearer配置:
.AddJwtBearer(options => { options.RequireHttpsMetadata = false; options.SaveToken = true; options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(jwtKey), ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = true }; // 添加Token验证后的自定义检查逻辑 options.Events = new JwtBearerEvents { OnTokenValidated = async context => { var dbContext = context.HttpContext.RequestServices.GetRequiredService<AppDbContext>(); // 获取Token中的userId var userIdClaim = context.Principal.FindFirst("userId") ?? context.Principal.FindFirst(JwtRegisteredClaimNames.Sub); if (userIdClaim == null || !Guid.TryParse(userIdClaim.Value, out var userId)) { context.Fail("Invalid user ID in token"); return; } // 查询用户的密码最后修改时间 var user = await dbContext.Users.FindAsync(userId); if (user == null) { context.Fail("User not found"); return; } // 解析Token的签发时间(iat声明) if (!long.TryParse(context.Principal.FindFirst(JwtRegisteredClaimNames.Iat)?.Value, out var issuedTimestamp)) { context.Fail("Invalid issued time in token"); return; } var issuedTime = DateTimeOffset.FromUnixTimeSeconds(issuedTimestamp).UtcDateTime; // 对比时间,拒绝密码修改前签发的Token if (user.PasswordLastModifiedTime > issuedTime) { context.Fail("Token was issued before password was changed. Please re-login."); } } }; });
2. 签发JWT时必须包含iat声明
生成Token时务必添加签发时间声明,否则上述验证会失败:
// 示例:生成JWT的核心代码 var claims = new[] { new Claim("userId", user.Id.ToString()), new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64), // 其他必要声明... }; var token = new JwtSecurityToken( claims: claims, expires: DateTime.UtcNow.AddHours(1), signingCredentials: new SigningCredentials(new SymmetricSecurityKey(jwtKey), SecurityAlgorithms.HmacSha256Signature) );
三、控制器中访问扩展后的用户信息
完成配置后,在控制器里直接通过User对象获取扩展后的Claims:
[Authorize] [ApiController] [Route("api/[controller]")] public class UserController : ControllerBase { [HttpGet("profile")] public IActionResult GetProfile() { var userId = User.FindFirst("userId")?.Value; var fullName = User.FindFirst(ClaimTypes.Name)?.Value; var email = User.FindFirst("Email")?.Value; var roles = User.Claims.Where(c => c.Type == ClaimTypes.Role).Select(c => c.Value).ToList(); return Ok(new { UserId = userId, FullName = fullName, Email = email, Roles = roles }); } }
内容的提问来源于stack exchange,提问作者Levy Barbosa
相关产品推荐
相关产品推荐

