using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.IdentityModel.Tokens;
public string GenerateSimpleToken()
{
var securityKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes("your-256-bit-secret"));
var credentials = new SigningCredentials(
securityKey, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: "your-issuer",
audience: "your-audience",
claims: new[] { new Claim(ClaimTypes.Name, "username") },
expires: DateTime.Now.AddMinutes(30),
signingCredentials: credentials);
return new JwtSecurityTokenHandler().WriteToken(token);
}
三、完整令牌服务实现
3.1 配置类设计
public class JwtSettings
{
public string Secret { get; set; }
public string Issuer { get; set; }
public string Audience { get; set; }
public int ExpireMinutes { get; set; }
public int RefreshTokenExpireDays { get; set; }
}
// appsettings.json配置
{
"Jwt": {
"Secret": "your-256-bit-secret-key",
"Issuer": "https://yourdomain.com",
"Audience": "https://yourdomain.com",
"ExpireMinutes": 30,
"RefreshTokenExpireDays": 7
}
}
3.2 令牌服务实现
public class JwtTokenService
{
private readonly JwtSettings _settings;
private readonly byte[] _secret;
public JwtTokenService(IOptions<JwtSettings> settings)
{
_settings = settings.Value;
_secret = Encoding.ASCII.GetBytes(_settings.Secret);
}
public string GenerateAccessToken(IEnumerable<Claim> claims)
{
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.UtcNow.AddMinutes(_settings.ExpireMinutes),
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(_secret),
SecurityAlgorithms.HmacSha256Signature),
Issuer = _settings.Issuer,
Audience = _settings.Audience
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
public string GenerateRefreshToken()
{
var randomNumber = new byte[32];
using var rng = RandomNumberGenerator.Create();
rng.GetBytes(randomNumber);
return Convert.ToBase64String(randomNumber);
}
public ClaimsPrincipal GetPrincipalFromToken(string token)
{
var tokenHandler = new JwtSecurityTokenHandler();
try
{
var principal = tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(_secret),
ValidateIssuer = true,
ValidIssuer = _settings.Issuer,
ValidateAudience = true,
ValidAudience = _settings.Audience,
ValidateLifetime = false // 过期令牌也能解析
}, out var validatedToken);
if (!IsJwtWithValidSecurityAlgorithm(validatedToken))
return null;
return principal;
}
catch
{
return null;
}
}
private bool IsJwtWithValidSecurityAlgorithm(SecurityToken validatedToken)
{
return validatedToken is JwtSecurityToken jwtSecurityToken &&
jwtSecurityToken.Header.Alg.Equals(
SecurityAlgorithms.HmacSha256,
StringComparison.InvariantCultureIgnoreCase);
}
}
四、声明(Claims)设计策略
4.1 标准声明类型
声明类型
描述
JWT规范字段
用户标识
用户唯一ID
sub
用户名
用户显示名称
unique_name
角色
用户角色列表
role
颁发时间
令牌创建时间
iat
过期时间
令牌过期时间
exp
生效时间
令牌生效时间
nbf
4.2 自定义声明实现
public IEnumerable<Claim> GenerateUserClaims(User user, IList<string> roles)
{
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, user.Id),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Email, user.Email),
new Claim(ClaimTypes.Name, user.UserName),
new Claim("department", user.Department),
new Claim("position", user.Position)
};
// 添加角色声明
foreach (var role in roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
// 添加权限声明
var permissions = _permissionService.GetUserPermissions(user.Id);
foreach (var permission in permissions)
{
claims.Add(new Claim("permission", permission));
}
return claims;
}
五、签名与加密方案
5.1 对称加密(HMAC)
// 使用256位密钥
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(
Configuration["Jwt:Secret"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
5.2 非对称加密(RSA)
// 从文件读取RSA私钥
var privateKey = File.ReadAllText("private-key.pem");
var rsa = RSA.Create();
rsa.ImportFromPem(privateKey);
var signingCredentials = new SigningCredentials(
new RsaSecurityKey(rsa),
SecurityAlgorithms.RsaSha256);
5.3 加密令牌(JWE)
var encryptingCredentials = new EncryptingCredentials(
new SymmetricSecurityKey(Encoding.UTF8.GetBytes("encryption-key")),
SecurityAlgorithms.Aes128KW,
SecurityAlgorithms.Aes128CbcHmacSha256);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
SigningCredentials = signingCredentials,
EncryptingCredentials = encryptingCredentials,
Expires = DateTime.UtcNow.AddHours(1)
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
[HttpPost("refresh")]
public async Task<IActionResult> RefreshToken([FromBody] RefreshTokenRequest request)
{
var principal = _tokenService.GetPrincipalFromExpiredToken(request.AccessToken);
if (principal == null)
return BadRequest("Invalid token");
var userId = principal.FindFirstValue(ClaimTypes.NameIdentifier);
var user = await _userManager.FindByIdAsync(userId);
if (user == null || user.RefreshToken != request.RefreshToken ||
user.RefreshTokenExpiryTime <= DateTime.UtcNow)
return BadRequest("Invalid refresh token");
var newClaims = await _claimsService.GetUserClaimsAsync(user);
var newAccessToken = _tokenService.GenerateAccessToken(newClaims);
var newRefreshToken = _tokenService.GenerateRefreshToken();
user.RefreshToken = newRefreshToken;
user.RefreshTokenExpiryTime = DateTime.UtcNow.AddDays(_jwtSettings.RefreshTokenExpireDays);
await _userManager.UpdateAsync(user);
return Ok(new TokenResponse
{
AccessToken = newAccessToken,
RefreshToken = newRefreshToken,
ExpiresIn = _jwtSettings.ExpireMinutes * 60
});
}
七、性能优化策略
7.1 令牌压缩
// 使用DEFLATE压缩Payload
public string GenerateCompressedToken(IEnumerable<Claim> claims)
{
var payload = new JwtPayload(
_settings.Issuer,
_settings.Audience,
claims,
DateTime.UtcNow,
DateTime.UtcNow.AddMinutes(_settings.ExpireMinutes));
using var outputStream = new MemoryStream();
using (var compressionStream = new DeflateStream(outputStream, CompressionLevel.Optimal))
{
using var writer = new StreamWriter(compressionStream);
writer.Write(payload.SerializeToJson());
}
var compressedPayload = Base64UrlEncoder.Encode(outputStream.ToArray());
var header = new JwtHeader(new SigningCredentials(
new SymmetricSecurityKey(_secret), SecurityAlgorithms.HmacSha256));
return $"{header.Base64UrlEncode()}.{compressedPayload}";
}
7.2 缓存验证结果
services.AddMemoryCache();
public class CachingJwtValidator : ISecurityTokenValidator
{
private readonly IJwtValidator _innerValidator;
private readonly IMemoryCache _cache;
public bool ValidateToken(
string token,
TokenValidationParameters validationParameters,
out SecurityToken validatedToken)
{
var cacheKey = $"jwt_validation_{token.GetHashCode()}";
if (_cache.TryGetValue(cacheKey, out validatedToken))
return true;
var result = _innerValidator.ValidateToken(
token, validationParameters, out validatedToken);
if (result)
{
var expiry = (validatedToken as JwtSecurityToken)?.ValidTo;
var cacheTime = expiry - DateTime.UtcNow - TimeSpan.FromMinutes(1);
_cache.Set(cacheKey, validatedToken, cacheTime.Value);
}
return result;
}
}
八、安全增强措施
8.1 令牌黑名单
public class TokenBlacklistService
{
private readonly IDistributedCache _cache;
public TokenBlacklistService(IDistributedCache cache)
{
_cache = cache;
}
public async Task BlacklistToken(string token, TimeSpan expiry)
{
var tokenId = GetTokenId(token);
await _cache.SetStringAsync(
$"blacklist_{tokenId}",
"revoked",
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = expiry
});
}
public async Task<bool> IsTokenRevoked(string token)
{
var tokenId = GetTokenId(token);
var result = await _cache.GetStringAsync($"blacklist_{tokenId}");
return result != null;
}
private string GetTokenId(string token)
{
var handler = new JwtSecurityTokenHandler();
var jwtToken = handler.ReadJwtToken(token);
return jwtToken.Id;
}
}
8.2 动态密钥轮换
public class KeyRotationService : IKeyRotationService
{
private readonly List<SymmetricSecurityKey> _activeKeys;
private readonly IConfiguration _config;
public KeyRotationService(IConfiguration config)
{
_config = config;
_activeKeys = LoadActiveKeys();
}
public IEnumerable<SymmetricSecurityKey> GetValidKeys()
{
return _activeKeys;
}
public void RotateKeys()
{
var newKey = GenerateNewKey();
_activeKeys.Insert(0, newKey);
// 保留最近3个有效密钥
if (_activeKeys.Count > 3)
_activeKeys.RemoveRange(3, _activeKeys.Count - 3);
}
private SymmetricSecurityKey GenerateNewKey()
{
var key = new byte[32];
using var rng = RandomNumberGenerator.Create();
rng.GetBytes(key);
return new SymmetricSecurityKey(key);
}
}
// 在验证时接受多个密钥
services.AddAuthentication()
.AddJwtBearer(options =>
{
options.TokenValidationParameters.IssuerSigningKeyResolver = (token, securityToken, kid, parameters) =>
{
return _keyRotationService.GetValidKeys();
};
});
九、测试与调试
9.1 单元测试示例
public class JwtTokenServiceTests
{
private readonly JwtTokenService _tokenService;
public JwtTokenServiceTests()
{
var config = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string>
{
["Jwt:Secret"] = "test-secret-12345678901234567890123456789012",
["Jwt:Issuer"] = "test-issuer",
["Jwt:Audience"] = "test-audience",
["Jwt:ExpireMinutes"] = "30"
})
.Build();
_tokenService = new JwtTokenService(Options.Create(config.GetSection("Jwt").Get<JwtSettings>()));
}
[Fact]
public void GenerateToken_ShouldReturnValidJwt()
{
// Arrange
var claims = new[] { new Claim(ClaimTypes.Name, "testuser") };
// Act
var token = _tokenService.GenerateAccessToken(claims);
// Assert
Assert.NotNull(token);
Assert.NotEmpty(token);
var handler = new JwtSecurityTokenHandler();
var jwt = handler.ReadJwtToken(token);
Assert.Equal("test-issuer", jwt.Issuer);
Assert.Contains(jwt.Claims, c => c.Type == ClaimTypes.Name && c.Value == "testuser");
}
}
9.2 调试令牌内容
// 解码JWT令牌
public JwtSecurityToken DecodeToken(string token)
{
var handler = new JwtSecurityTokenHandler();
return handler.ReadJwtToken(token);
}
// 打印令牌信息
public void PrintTokenInfo(string token)
{
var jwt = DecodeToken(token);
Console.WriteLine($"Issuer: {jwt.Issuer}");
Console.WriteLine($"Audience: {string.Join(",", jwt.Audiences)}");
Console.WriteLine($"Valid From: {jwt.ValidFrom}");
Console.WriteLine($"Valid To: {jwt.ValidTo}");
Console.WriteLine("\nClaims:");
foreach (var claim in jwt.Claims)
{
Console.WriteLine($"{claim.Type}: {claim.Value}");
}
}