共计 3019 个字符,预计需要花费 8 分钟才能阅读完成。
背景与痛点
在传统的 Web 应用中,我们通常使用 Session 来管理用户认证状态。Session 机制通过在服务端存储用户状态,客户端仅保存一个 Session ID。这种方式虽然简单,但在现代分布式系统中暴露出诸多问题:

- 扩展性差:Session 通常存储在服务器内存中,当应用需要水平扩展时,需要引入额外的 Session 共享机制
- CSRF 风险:基于 Cookie 的 Session 容易受到跨站请求伪造攻击
- 移动端不友好:移动应用往往需要显式的认证令牌而非 Cookie
JWT(JSON Web Token)作为一种无状态的认证方案,完美解决了这些问题。它的核心优势在于:
- 自包含:所有必要信息都包含在 Token 本身中
- 无状态:服务端不需要存储会话信息
- 跨域友好:易于在各种客户端(Web、移动端、API)中使用
技术选型
在选择认证方案时,我们需要考虑应用场景和技术特点:
| 方案 | 适用场景 | 主要特点 |
|---|---|---|
| JWT | API 认证、无状态服务 | 轻量、自包含、易于跨服务使用 |
| OAuth 2.0 | 第三方授权、社会化登录 | 复杂但标准化,适合开放平台 |
| Cookie | 传统 Web 应用 | 浏览器自动管理,但存在 CSRF 风险 |
对于大多数 C# Web API 项目,JWT 提供了最佳平衡点:足够安全又不会引入过多复杂性。
核心实现
1. 安装必要的 NuGet 包
首先需要安装 JWT 相关的 NuGet 包:
dotnet add package System.IdentityModel.Tokens.Jwt
2. 配置 JWT 参数
在 appsettings.json 中添加 JWT 配置:
{
"Jwt": {
"Key": "your-secret-key-at-least-32-characters",
"Issuer": "your-issuer",
"Audience": "your-audience",
"ExpireMinutes": 30
}
}
3. 生成 Token 的服务
创建一个服务类负责 Token 的生成和验证:
public class JwtService
{
private readonly IConfiguration _configuration;
public JwtService(IConfiguration configuration)
{_configuration = configuration;}
public string GenerateToken(User user)
{
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var claims = new[]
{new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(ClaimTypes.Role, user.Role)
};
var token = new JwtSecurityToken(issuer: _configuration["Jwt:Issuer"],
audience: _configuration["Jwt:Audience"],
claims: claims,
expires: DateTime.Now.AddMinutes(Convert.ToDouble(_configuration["Jwt:ExpireMinutes"])),
signingCredentials: credentials);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}
4. 登录接口实现
在控制器中添加登录接口:
[HttpPost("login")]
public IActionResult Login([FromBody] LoginModel model)
{
// 1. 验证用户凭据
var user = _userService.Authenticate(model.Username, model.Password);
if (user == null)
return Unauthorized();
// 2. 生成 Token
var token = _jwtService.GenerateToken(user);
// 3. 返回 Token
return Ok(new { Token = token});
}
5. 配置 JWT 验证中间件
在 Program.cs 中添加 JWT 认证服务:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
};
});
然后在请求管道中启用认证中间件:
app.UseAuthentication();
app.UseAuthorization();
安全考量
1. 防止 Token 被盗用
- 始终使用 HTTPS 传输 Token
- 设置合理的 Token 过期时间(建议 15-30 分钟)
- 避免在 URL 中传递 Token
- 考虑添加 IP 绑定等额外验证
2. 敏感信息存储
- 不要在 Token 中存储敏感信息(如密码)
- 即使加密,也应最小化 Token 中的用户信息
- 考虑使用引用 Token 模式(Reference Token)处理高敏感场景
3. 防范重放攻击
- 使用
jti(JWT ID)声明防止 Token 重用 - 实现短期有效的 Token 过期策略
- 考虑添加时间戳验证
生产环境建议
性能优化
- 使用非对称加密(RS256)减少服务端计算负担
- 实现 Token 刷新机制减少频繁认证
- 考虑使用分布式缓存存储吊销 Token 列表
常见错误排查
- Token 验证失败:检查 Issuer、Audience 和签名密钥是否匹配
- Token 过期:确保客户端和服务器时间同步
- 无效签名:验证密钥是否正确且未更改
监控与日志
- 记录失败的认证尝试
- 监控 Token 生成和验证的性能
- 设置 Token 使用率告警
思考题
在分布式系统中,如何实现 Token 的吊销机制?常见方案包括:
- 使用短期有效的 Token 配合刷新 Token
- 维护吊销 Token 列表(黑名单)
- 基于事件的吊销机制(如用户密码修改时吊销所有 Token)
- 使用 Redis 等高速存储维护 Token 状态
每种方案都有其适用场景和权衡,需要根据具体业务需求选择最合适的方案。
正文完
