共计 3955 个字符,预计需要花费 10 分钟才能阅读完成。
背景与痛点:为什么需要 JWT
在传统的 Web 应用中,Session 认证是常见的用户身份验证方式。但随着前后端分离架构和微服务的普及,Session 暴露了明显短板:

- 服务器内存依赖:Session 数据存储在服务器内存中,横向扩展时需额外处理 Session 共享
- 跨域限制:Cookie 默认不支持跨域,对多端接入不友好
- CSRF 风险:基于 Cookie 的机制容易遭受跨站请求伪造攻击
JWT(JSON Web Token)通过标准化令牌解决了这些问题:
- 无状态:所有认证信息包含在 Token 中,服务端无需存储
- 跨域友好:通过 Authorization 头传输,天然支持跨域
- 自包含:Payload 可存储业务相关 claims,减少数据库查询
JWT 结构深度解析
一个标准的 JWT 由三部分组成,通过点号连接:
Header.Payload.Signature
1. Header(头部)
通常包含令牌类型和签名算法,例如:
{
"alg": "HS256",
"typ": "JWT"
}
2. Payload(负载)
包含 claims(声明),分为三类:
- Registered claims:预定义字段如 iss(签发者)、exp(过期时间)
- Public claims:公开定义的字段
- Private claims:业务自定义字段
示例:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"iat": 1516239022
}
3. Signature(签名)
对前两部分 Base64 编码后的字符串进行签名,防止篡改。HS256 算法示例如下:
HMACSHA256(base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
.NET 6 实战:从配置到实现
第一步:NuGet 包准备
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
第二步:Startup 配置(Program.cs)
// WARNING: 生产环境必须使用强密钥且长度 >=256 位
var secretKey = "your-256-bit-secret";
var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey));
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = "your-domain.com",
ValidateAudience = true,
ValidAudience = "your-audience",
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero, // 严格过期时间检查
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey
};
// 处理 401 响应格式
options.Events = new JwtBearerEvents()
{
OnAuthenticationFailed = context =>
{
context.Response.StatusCode = 401;
return Task.CompletedTask;
}
};
});
第三步:实现登录接口
[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
private readonly UserManager<IdentityUser> _userManager;
private readonly IConfiguration _config;
public AuthController(
UserManager<IdentityUser> userManager,
IConfiguration config)
{
_userManager = userManager;
_config = config;
}
[HttpPost("login")]
public async Task<IActionResult> Login([FromBody] LoginModel model)
{
// 1. 验证用户
var user = await _userManager.FindByNameAsync(model.Username);
if (user == null || !await _userManager.CheckPasswordAsync(user, model.Password))
return Unauthorized();
// 2. 生成 Claims
var authClaims = new List<Claim>
{new(ClaimTypes.Name, user.UserName),
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
// 添加自定义 claims
new("Department", "IT")
};
// 3. 获取用户角色并添加
var roles = await _userManager.GetRolesAsync(user);
authClaims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));
// 4. 生成 Token
var token = new JwtSecurityToken(issuer: _config["JWT:ValidIssuer"],
audience: _config["JWT:ValidAudience"],
expires: DateTime.Now.AddHours(3),
claims: authClaims,
signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["JWT:Secret"])),
SecurityAlgorithms.HmacSha256)
);
return Ok(new
{token = new JwtSecurityTokenHandler().WriteToken(token),
expiration = token.ValidTo
});
}
}
高级安全实践
1. Token 刷新机制
// 在登录响应中返回 refreshToken
var refreshToken = GenerateRefreshToken();
await _userManager.SetAuthenticationTokenAsync(
user,
"MyApp",
"RefreshToken",
refreshToken);
// 刷新接口实现
[HttpPost("refresh")]
public async Task<IActionResult> Refresh([FromBody] RefreshModel model)
{var principal = GetPrincipalFromExpiredToken(model.AccessToken);
var username = principal.Identity?.Name;
var user = await _userManager.FindByNameAsync(username);
if (user == null ||
await _userManager.GetAuthenticationTokenAsync(user, "MyApp", "RefreshToken") != model.RefreshToken)
return BadRequest();
return GenerateNewToken(user);
}
2. 防范重放攻击
- 在 Payload 中添加 jti (JWT ID)唯一标识
- 服务端维护短期 Token 黑名单
- 限制相同 Token 的使用次数
3. 敏感信息规范
- 绝对不要 在 Payload 中存储密码等敏感信息
- 用户 ID 建议使用 sub (subject) claim 而非直接放用户名
- 生产环境必须使用 RSA 非对称加密(RS256/RS512)
性能优化策略
算法选择对比
| 算法 | 类型 | 性能 | 安全性 | 适用场景 |
|---|---|---|---|---|
| HS256 | 对称 | 高 | 中 | 内部服务 |
| RS256 | 非对称 | 中 | 高 | 公开 API |
Token 存储策略
- 无状态模式:完全依赖 Token 自包含(推荐)
- 白名单模式:仅验证活跃 Token(折中方案)
- 黑名单模式:记录失效 Token(高安全需求)
避坑指南
常见配置错误
- Issuer/Audience 不匹配:确保验证参数与签发参数一致
- 未设置 ClockSkew:默认有 5 分钟时间容差,严格场景应设为 Zero
- 弱签名密钥:HS256 密钥长度必须≥256 位
生产环境必须项
- 强制 HTTPS(JWT 在明文传输中可能被截获)
- 设置合理的 Token 过期时间(通常 1 - 4 小时)
- 实现完善的日志记录(记录可疑 Token 使用)
延伸阅读
通过本文的实践,我们构建了一个符合生产要求的 JWT 认证系统。关键是要理解:JWT 不是银弹,需要根据业务场景合理设计安全策略。建议在重要系统中结合 OAuth 2.0 或 OpenID Connect 提供更完整的身份解决方案。
正文完
