三、JWT(JSON Web Tokens)令牌(token)
1. JWT基础概念与工作原理JSON Web TokensJWT本质上是一种开放标准RFC 7519它定义了一种紧凑且自包含的方式用于在各方之间安全地传输信息。想象一下JWT就像一张演唱会门票票面印刷了观众座位信息Payload票务公司的防伪标识Signature以及票面格式说明Header。这三部分共同构成了一个完整的凭证系统。JWT的典型结构由三部分组成用点号(.)连接Header说明令牌类型和签名算法{ alg: HS256, typ: JWT }Payload携带实际数据如用户ID、角色等{ sub: 1234567890, name: John Doe, admin: true }Signature对前两部分的签名验证实际生成的JWT看起来是这样的字符串eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c与传统的Session认证相比JWT的核心优势在于无状态性。服务器不需要维护会话存储每个请求都自带完整的认证信息。这特别适合分布式系统和微服务架构但也要注意JWT一旦签发就无法中途废止的特性。2. .NET Core中的JWT配置实战2.1 基础配置在.NET Core项目中我们首先需要在appsettings.json中配置JWT参数{ JwtSetting: { Issuer: your_issuer, Audience: your_audience, SecretKey: your_256bit_secret_key_here! } }这里有几个关键点需要注意SecretKey长度建议至少32字符256位Issuer和Audience用于验证令牌的颁发者和目标受众生产环境应该通过机密管理器存储密钥2.2 用户模型设计创建用户信息模型是定义JWT携带数据的关键步骤public class TokenModel { public string UserId { get; set; } public string UserName { get; set; } public string Roles { get; set; } // 多个角色用逗号分隔 public long ExpireTime { get; set; } // 建议使用Unix时间戳 }这种设计允许我们在令牌中携带用户基础信息和权限数据避免频繁查询数据库。3. JWT核心功能实现3.1 令牌颁发实现创建JwtHelper类处理核心逻辑public static class JwtHelper { public static string GenerateToken(TokenModel tokenModel) { var claims new ListClaim { new Claim(JwtRegisteredClaimNames.Jti, tokenModel.UserId), new Claim(ClaimTypes.Name, tokenModel.UserName), new Claim(JwtRegisteredClaimNames.Exp, tokenModel.ExpireTime.ToString()) }; // 添加角色声明 claims.AddRange(tokenModel.Roles.Split(,).Select(role new Claim(ClaimTypes.Role, role.Trim()))); var key new SymmetricSecurityKey( Encoding.UTF8.GetBytes(Configuration[JwtSetting:SecretKey])); var creds new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token new JwtSecurityToken( issuer: Configuration[JwtSetting:Issuer], audience: Configuration[JwtSetting:Audience], claims: claims, expires: DateTime.Now.AddHours(2), signingCredentials: creds); return new JwtSecurityTokenHandler().WriteToken(token); } }3.2 令牌解析验证实现令牌的反向解析public static TokenModel ParseToken(string jwtToken) { var handler new JwtSecurityTokenHandler(); var token handler.ReadJwtToken(jwtToken); return new TokenModel { UserId token.Claims.First(c c.Type JwtRegisteredClaimNames.Jti).Value, UserName token.Claims.First(c c.Type ClaimTypes.Name).Value, Roles string.Join(,, token.Claims .Where(c c.Type ClaimTypes.Role) .Select(c c.Value)) }; }4. API集成与Swagger配置4.1 登录接口实现创建用户控制器处理认证[ApiController] [Route(api/[controller])] public class AuthController : ControllerBase { [HttpPost(login)] public IActionResult Login([FromBody] LoginDto loginDto) { // 实际项目应该验证用户名密码 var tokenModel new TokenModel { UserId 123, UserName loginDto.Username, Roles User,Admin, ExpireTime DateTimeOffset.Now.AddHours(2).ToUnixTimeSeconds() }; var token JwtHelper.GenerateToken(tokenModel); return Ok(new { token }); } }4.2 Swagger集成在Program.cs中配置Swagger支持JWTbuilder.Services.AddSwaggerGen(c { c.AddSecurityDefinition(Bearer, new OpenApiSecurityScheme { Description JWT Authorization header, Name Authorization, In ParameterLocation.Header, Type SecuritySchemeType.ApiKey, Scheme Bearer }); c.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference new OpenApiReference { Type ReferenceType.SecurityScheme, Id Bearer } }, Array.Emptystring() } }); });这样在Swagger UI中会出现Authorize按钮方便测试接口。5. 认证与授权配置5.1 服务注册创建专门的配置类设置认证服务public static class AuthConfig { public static void ConfigureAuth(this IServiceCollection services, IConfiguration config) { services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options { options.TokenValidationParameters new TokenValidationParameters { ValidateIssuer true, ValidateAudience true, ValidateLifetime true, ValidateIssuerSigningKey true, ValidIssuer config[JwtSetting:Issuer], ValidAudience config[JwtSetting:Audience], IssuerSigningKey new SymmetricSecurityKey( Encoding.UTF8.GetBytes(config[JwtSetting:SecretKey])), ClockSkew TimeSpan.Zero // 严格校验过期时间 }; options.Events new JwtBearerEvents { OnAuthenticationFailed context { if (context.Exception.GetType() typeof(SecurityTokenExpiredException)) { context.Response.Headers.Add(Token-Expired, true); } return Task.CompletedTask; } }; }); } }5.2 中间件配置在请求管道中启用认证中间件app.UseRouting(); app.UseAuthentication(); app.UseAuthorization();注意中间件顺序非常重要Authentication必须放在Authorization之前。6. 权限控制实战6.1 基于角色的控制在控制器方法上添加权限控制[Authorize(Roles Admin)] [HttpGet(admin-only)] public IActionResult AdminEndpoint() { return Ok(This is admin only content); }6.2 策略授权对于更复杂的权限控制可以使用策略services.AddAuthorization(options { options.AddPolicy(RequireAdminOrModerator, policy policy.RequireRole(Admin, Moderator)); options.AddPolicy(MinimumAge18, policy policy.RequireClaim(DateOfBirth, DateTime.Now.AddYears(-18).ToString(yyyy-MM-dd))); });然后在控制器方法上使用[Authorize(Policy RequireAdminOrModerator)]7. 安全最佳实践密钥管理永远不要将密钥硬编码在代码中使用密钥管理系统有效期控制设置合理的过期时间通常15-30分钟HTTPS加密确保所有JWT传输都通过HTTPS令牌存储前端应使用HttpOnly的Cookie或安全存储敏感数据不要在Payload中存放密码等敏感信息刷新令牌实现刷新令牌机制减少长期有效的风险一个典型的刷新令牌流程如下客户端使用用户名密码获取访问令牌和刷新令牌访问令牌过期后使用刷新令牌获取新访问令牌刷新令牌应有更长有效期但可被撤销在实现JWT方案时我曾遇到一个典型问题当用户权限变更后旧的JWT仍然有效直到过期。解决方案是结合短期有效的JWT和实时权限检查或者在权限变更时维护一个令牌黑名单。