JWT 在 .NET 中的实现与微服务认证实践
一、JWT 核心概念
JSON Web Token 是一种基于 JSON 的开放标准(RFC 7519),用于在网络应用间安全传输声明信息。其特点在于紧凑、自包含,且可通过数字签名验证信息真实性。
1.1 令牌结构解析
JWT 由三个 Base64Url 编码的部分通过点号连接而成:
- Header:声明签名算法与令牌类型
- Payload:承载实际业务数据
- Signature:对前两部分进行加密签名
典型示例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
1.2 认证方案演进对比
| 方案 | 机制 | 适用场景 |
|---|---|---|
| 传统 Session | 服务端存储会话状态,Cookie 传递标识 | 单体应用 |
| 分布式 Session | Redis 集中管理会话数据 | 集群部署 |
| JWT 认证 | 客户端持有令牌,服务端无状态验证 | 微服务/分布式架构 |
JWT 方案优势:跨语言支持、减少数据库查询、天然适配分布式系统、传输高效。
二、基础操作实现
2.1 生成令牌
public static string BuildToken()
{
var identityClaims = new List<Claim>();
identityClaims.Add(new Claim(ClaimTypes.NameIdentifier, "10086"));
identityClaims.Add(new Claim(ClaimTypes.Name, "TechExplorer"));
identityClaims.Add(new Claim(ClaimTypes.Role, "Editor"));
identityClaims.Add(new Claim(ClaimTypes.Role, "Reviewer"));
identityClaims.Add(new Claim("DepartmentCode", "RD-2024"));
var secret = "YourSecretKeyMustBeAtLeast16Chars!";
var expiry = DateTime.Now.AddHours(2);
var keyBytes = Encoding.UTF8.GetBytes(secret);
var symmetricKey = new SymmetricSecurityKey(keyBytes);
var signingCredentials = new SigningCredentials(symmetricKey, SecurityAlgorithms.HmacSha256);
var tokenDescriptor = new JwtSecurityToken(
claims: identityClaims,
expires: expiry,
signingCredentials: signingCredentials
);
return new JwtSecurityTokenHandler().WriteToken(tokenDescriptor);
}
2.2 解析载荷(无需密钥)
public static void ExtractPayload(string tokenString)
{
var segments = tokenString.Split('.');
var decodedHeader = Base64UrlDecode(segments[0]);
var decodedPayload = Base64UrlDecode(segments[1]);
Console.WriteLine($"Header: {decodedHeader}");
Console.WriteLine($"Payload: {decodedPayload}");
}
private static string Base64UrlDecode(string input)
{
input = input.Replace('-', '+').Replace('_', '/');
switch (input.Length % 4)
{
case 2: input += "=="; break;
case 3: input += "="; break;
}
var bytes = Convert.FromBase64String(input);
return Encoding.UTF8.GetString(bytes);
}
安全提示:Payload 仅为 Base64 编码,任何人均可解码查看。切勿将敏感信息直接存入其中。
2.3 签名验证与数据提取
public static Dictionary<string, string> VerifyAndExtract(string token)
{
var secretKey = "YourSecretKeyMustBeAtLeast16Chars!";
var handler = new JwtSecurityTokenHandler();
var validationParams = new TokenValidationParameters
{
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey)),
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true
};
var claimsPrincipal = handler.ValidateToken(token, validationParams, out _);
return claimsPrincipal.Claims.ToDictionary(
c => c.Type,
c => c.Value
);
}
三、.NET Core 集成方案
3.1 配置定义
在 appsettings.json 中添加:
{
"AuthSettings": {
"Secret": "d0ecd23c-dfdb-4005-a2ea-0fea210c858a",
"Issuer": "MyAppPlatform",
"Audience": "MyAppClient",
"AccessTokenLifetime": 30
}
}
3.2 配置实体
public class AuthSettings : IOptions<AuthSettings>
{
public AuthSettings Value => this;
public string Secret { get; set; }
public string Issuer { get; set; }
public string Audience { get; set; }
public int AccessTokenLifetime { get; set; }
public DateTime IssuedAt => DateTime.UtcNow;
public DateTime Expiration => IssuedAt.AddMinutes(AccessTokenLifetime);
private SecurityKey GetSecurityKey() =>
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Secret));
public SigningCredentials GetSigningCredentials() =>
new SigningCredentials(GetSecurityKey(), SecurityAlgorithms.HmacSha256);
}
3.3 令牌服务
public class TokenService
{
private readonly AuthSettings _settings;
public TokenService(IOptions<AuthSettings> options)
{
_settings = options.Value;
}
public string IssueToken(UserProfile user)
{
var claims = new List<Claim>();
claims.Add(new Claim("mobile", user.MobileNumber));
claims.Add(new Claim(ClaimTypes.NameIdentifier, user.UserId));
claims.Add(new Claim(ClaimTypes.Name, user.DisplayName));
foreach (var permission in user.Permissions.Split(','))
{
claims.Add(new Claim(ClaimTypes.Role, permission.Trim()));
}
var token = new JwtSecurityToken(
issuer: _settings.Issuer,
audience: _settings.Audience,
claims: claims,
notBefore: _settings.IssuedAt,
expires: _settings.Expiration,
signingCredentials: _settings.GetSigningCredentials()
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}
3.4 认证中间件配置
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<TokenService>();
services.Configure<AuthSettings>(Configuration.GetSection("AuthSettings"));
var authConfig = new AuthSettings();
Configuration.GetSection("AuthSettings").Bind(authConfig);
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidIssuer = authConfig.Issuer,
ValidAudience = authConfig.Audience,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(authConfig.Secret)),
ValidateLifetime = true,
ClockSkew = TimeSpan.FromSeconds(10)
};
});
}
public void Configure(IApplicationBuilder app)
{
app.UseAuthentication();
app.UseAuthorization();
}
3.5 API 控制器应用
[Route("api/[controller]")]
[ApiController]
public class AccountController : ControllerBase
{
private readonly TokenService _tokenService;
public AccountController(TokenService tokenService)
{
_tokenService = tokenService;
}
[HttpPost("sign-in")]
[AllowAnonymous]
public IActionResult Authenticate([FromBody] LoginRequest request)
{
// 执行身份验证逻辑...
var profile = new UserProfile
{
UserId = request.UserId,
DisplayName = request.UserName,
MobileNumber = request.Phone,
Permissions = "editor,reviewer"
};
var accessToken = _tokenService.IssueToken(profile);
return Ok(new { token = accessToken });
}
[HttpGet("profile")]
[Authorize(Roles = "editor")]
public IActionResult GetProfile()
{
var userInfo = new UserProfile
{
UserId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value,
DisplayName = User.FindFirst(ClaimTypes.Name)?.Value,
Permissions = string.Join(",",
User.FindAll(ClaimTypes.Role).Select(r => r.Value))
};
return Ok(userInfo);
}
[HttpGet("admin-only")]
[Authorize(Roles = "admin")]
public IActionResult AdminEndpoint()
{
return Ok("Authorized");
}
}
3.6 Swagger 集成认证
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo { Title = "API", Version = "v1" });
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "请输入: Bearer {token}",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer"
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
Array.Empty<string>()
}
});
});
四、关键注意事项
- 密钥长度必须达到算法要求(HMAC-SHA256 建议至少 32 字节)
- 令牌载荷虽可读取但不可信任,必须通过签名验证其完整性
- 合理设置过期时间,结合 Refresh Token 机制实现安全与体验的平衡
- 对于高安全性要求的场景,考虑使用 RSA 等非对称加密算法