.NET的JWT密钥管理 - 为您的Jwt(jws) / Jwe生成和自动轮换加密密钥
密钥管理最大的问题之一是:如何安全地分发密钥。HMAC依赖于在多个项目之间共享密钥。为了实现这一点,NetDevPack.Security.Jwt
使用公钥加密系统来生成您的密钥。因此您可以在https://<your_api_adrress>/jwks
共享您的公钥!
您是这样创建Jwt的吗?
让我告诉您:您有一个问题。
该项目的目标是通过管理您的JWT来帮助提高应用程序的安全性。
- 自动创建RSA或ECDsa密钥
- 支持JWE
- 支持公共
jwks_uri
端点,以JWKS格式提供您的公钥 - 为您的客户端API提供扩展以使用JWKS端点。详见NetDevack.Security.JwtExtensions
- 每90天自动轮换密钥(遵循NIST公钥轮换最佳实践)
- 密钥轮换后删除旧的私钥(NIST建议)
- 使用RSA和ECDSA的推荐设置(RFC 7518建议)
- 使用随机数生成器为带AES CBC的JWE生成密钥(dotnet不支持带Aes128GCM的RSA-OAEP)
- 默认将密钥保存在与ASP.NET DataProtection相同的位置(ASP.NET保存用于加密MVC cookie的密钥的同一位置)
它使用RSA和ECDsa算法生成更好的密钥。这是RFC 7518最推荐的。
令牌验证
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "https://www.devstore.academy",
ValidAudience = "NetDevPack.Security.Jwt.AspNet"
};
});
builder.Services.AddAuthorization();
builder.Services.AddJwksManager().UseJwtValidation();
生成令牌:
public AuthController(IJwtService jwtService)
{
_jwtService = jwtService;
}
private string GenerateToken(User user)
{
var key = _jwtService.GetCurrentSigningCredentials(); // (ECDsa或RSA)自动生成的密钥
var handler = new JsonWebTokenHandler();
var now = DateTime.Now;
var descriptor = new SecurityTokenDescriptor
{
Issuer = "https://www.devstore.academy", // <- 您的网站
Audience = "NetDevPack.Security.Jwt.AspNet",
IssuedAt = now,
NotBefore = now,
Expires = now.AddMinutes(60),
Subject = new ClaimsIdentity(FakeClaims.GenerateClaim().Generate(5)),
SigningCredentials = await service.GetCurrentSigningCredentials()
};
return tokenHandler.WriteToken(token);
}
目录
- .NET的JWT密钥管理 - 为您的Jwt生成和自动轮换加密密钥
- 🛡️ 这是什么
- ℹ️ 安装
- ❤️ 令牌生成
- ✔️ 令牌验证 (Jws)
- ⛅ 多个API - 使用Jwks
- 💾 存储
- 示例
- 更改算法
- IdentityServer4 - 自动jwks_uri管理
- 为什么
- 许可证
🛡️ 这是什么
JSON Web密钥集(JWKS)是用于验证授权服务器颁发的JSON Web令牌(JWT)的公钥集合。该组件的主要目标是为您的JWKS提供集中存储和密钥轮换,同时遵循JWKS生成的最佳实践。它具有IdentityServer4插件,可以每90天自动轮换jwks_uri,并无缝管理您的jwks_uri。
如果您的API或OAuth 2.0部署在Kubernetes或Docker Swarm的负载均衡器下,则此组件至关重要。它的功能类似于ASP.NET Core中的DataProtection Key。
该组件生成、存储和管理您的JWKS,同时维护跨实例可访问的集中存储。默认情况下,每三个月生成一个新密钥。
您可以通过JWKS端点公开您的JWKS并与您的API共享。
ℹ️ 安装
要在您的API中安装NetDevPack.Security.Jwt
,请在NuGet包管理器控制台中使用以下命令:
Install-Package NetDevPack.Security.Jwt
或者,您可以使用.NET Core命令行界面:
dotnet add package NetDevPack.Security.Jwt
接下来,修改Startup.cs
或program.cs
文件中的Configure方法:
builder.Services.AddJwksManager().UseJwtValidation();
❤️ 令牌生成
在大多数情况下,当我们说JWT时,实际上是指JWS。
public AuthController(IJwtService jwtService)
{
_jwtService = jwtService;
}
private string GenerateToken(User user)
{
var tokenHandler = new JwtSecurityTokenHandler();
var currentIssuer = $"{ControllerContext.HttpContext.Request.Scheme}://{ControllerContext.HttpContext.Request.Host}";
var key = _jwtService.GetCurrentSigningCredentials(); // (ECDsa或RSA)自动生成的密钥
var token = tokenHandler.CreateToken(new SecurityTokenDescriptor
{
Issuer = currentIssuer,
Subject = identityClaims,
Expires = DateTime.UtcNow.AddHours(1),
SigningCredentials = key
});
return tokenHandler.WriteToken(token);
}
✔️ 令牌验证 (JWS)
使用相同的服务获取当前密钥并验证令牌。
public AuthController(IJwtService jwtService)
{
_jwtService = jwtService;
}
private string ValidateToken(string jwt)
{
var handler = new JsonWebTokenHandler();
var currentIssuer = $"{ControllerContext.HttpContext.Request.Scheme}://{ControllerContext.HttpContext.Request.Host}";
var result = handler.ValidateToken(jwt,
new TokenValidationParameters
{
ValidIssuer = currentIssuer,
SigningCredentials = _jwtService.GetCurrentSigningCredentials()
});
result.IsValid.Should().BeTrue();
}
⛅ 多个API - 使用Jwks
密钥管理的一个主要挑战是安全地分发密钥。HMAC依赖于在多个项目之间共享密钥。为解决这个问题,NetDevPack.Security.Jwt
使用公钥密码系统来生成密钥。因此,您可以在https://<your_api_address>/jwks
共享您的公钥!
简单明了 🎂
身份API(发出令牌的API)
在发出JWT令牌的API中安装NetDevPack.Security.Jwt.AspNetCore
。修改您的Startup.cs:
public void Configure(IApplicationBuilder app)
{
app.UseJwksDiscovery().UseJwtValidation();
}
生成令牌:
private string EncodeToken(ClaimsIdentity identityClaims)
{
var tokenHandler = new JwtSecurityTokenHandler();
var currentIssuer = $"{ControllerContext.HttpContext.Request.Scheme}://{ControllerContext.HttpContext.Request.Host}";
var key = _jwksService.GetCurrentSigningCredentials();
var token = tokenHandler.CreateToken(new SecurityTokenDescriptor
{
Issuer = currentIssuer,
Subject = identityClaims,
Expires = DateTime.UtcNow.AddHours(1),
SigningCredentials = key
});
return tokenHandler.WriteToken(token);
}
客户端API
在需要JWT验证的客户端API中,安装NetDevPack.Security.JwtExtensions
。然后,更新您的Startup.cs
:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(x =>
{
x.RequireHttpsMetadata = true;
x.SaveToken = true; // 将公钥保存在缓存中10分钟。
x.IncludeErrorDetails = true; // <- 便于调试
x.SetJwksOptions(new JwkOptions("https://localhost:5001/jwks"));
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// ...
app.UseAuthentication();
app.UseAuthorization();
// ...
}
在你的Controller
中:
[Authorize]
public class IdentityController : ControllerBase
{
public IActionResult Get()
{
return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
}
}
完成 👌!
💾 存储
默认情况下,NetDevPack.Security.Jwt
将密钥存储在与ASP.NET Core存储其加密密钥材料相同的位置。它使用IXmlRepository。
对DataProtection所做的任何更改也将适用于此。
你可以通过添加另一个提供程序并根据需要进行自定义来覆盖默认行为。
数据库
NetDevPack.Security.Jwt
包提供了一种使用EntityFramework Core在数据库中存储密钥的方法。
通过NuGet包管理器安装:
Install-Package NetDevPack.Security.Jwt.Store.EntityFrameworkCore
或通过.NET Core命令行界面:
dotnet add package NetDevPack.Security.Jwt.Store.EntityFrameworkCore
将ISecurityKeyContext
添加到你的DbContext:
class MyKeysContext : DbContext, ISecurityKeyContext
{
public MyKeysContext(DbContextOptions<MyKeysContext> options) : base(options) { }
// 这映射到存储密钥的表。
public DbSet<SecurityKeyWithPrivate> DataProtectionKeys { get; set; }
}
然后在Startup.cs
中更改你的配置
public void ConfigureServices(IServiceCollection services)
{
services.AddJwksManager().PersistKeysToDatabaseStore<MyKeysContext>();
}
完成!
文件系统
NetDevPack.Security.Jwt
包提供了一种将密钥存储到文件系统的机制。
安装
Install-Package NetDevPack.Security.Jwt.Store.FileSystem
或通过.NET Core命令行界面:
dotnet add package NetDevPack.Security.Jwt.Store.FileSystem
现在更改你的startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddJwksManager().PersistKeysToFileSystem(new DirectoryInfo(@"c:\temp-keys\"));
}
示例
你可以在这里找到几个示例
更改算法
可以在配置过程中修改默认算法。
build.Services.AddJwksManager(o =>
{
o.Jws = Algorithm.Create(DigitalSignaturesAlgorithm.RsaSsaPssSha256);
o.Jwe = Algorithm.Create(EncryptionAlgorithmKey.RsaOAEP).WithContentEncryption(EncryptionAlgorithmContent.Aes128CbcHmacSha256);
});
默认情况下,它使用根据RFC7518推荐的算法
build.Services.AddJwksManager(o =>
{
o.Jws { get; set; } = Algorithm.Create(AlgorithmType.RSA, JwtType.Jws);
o.Jwe { get; set; } = Algorithm.Create(AlgorithmType.RSA, JwtType.Jwe);
}
Algorithm对象提供了多种选择。
Jws
算法:
简称 | 名称 |
---|---|
HS256 | Hmac Sha256 |
HS384 | Hmac Sha384 |
HS512 | Hmac Sha512 |
RS256 | Rsa Sha256 |
RS384 | Rsa Sha384 |
RS512 | Rsa Sha512 |
PS256 | Rsa SsaPss Sha256 |
PS384 | Rsa SsaPss Sha384 |
PS512 | Rsa SsaPss Sha512 |
ES256 | Ecdsa Sha256 |
ES384 | Ecdsa Sha384 |
ES512 | Ecdsa Sha512 |
Jwe
算法选项:
简称 | 密钥管理算法 |
---|---|
RSA1_5 | RSA1_5 |
RsaOAEP | RSAES OAEP using |
A128KW | A128KW |
A256KW | A256KW |
加密选项
简称 | 内容加密算法 |
---|---|
Aes128CbcHmacSha256 | A128CBC-HS256 |
Aes192CbcHmacSha384 | A192CBC-HS384 |
Aes256CbcHmacSha512 | A256CBC-HS512 |
IdentityServer4 - 自动jwks_uri管理
NetDevPack.Security.Jwt
提供IdentityServer4
密钥材料。它自动生成和轮换密钥。
首先安装
Install-Package NetDevPack.Security.Jwt.IdentityServer4
或通过.NET Core命令行界面:
dotnet add package NetDevPack.Security.Jwt.IdentityServer4
转到Startup.cs
public void ConfigureServices(IServiceCollection services)
{
var builder = services.AddIdentityServer()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApis())
.AddInMemoryClients(Config.GetClients());
services.AddJwksManager().IdentityServer4AutoJwksManager();
}
如果你想使用数据库,请按照DatabaseStore的说明进行操作。
为什么
在使用OAuth 2.0或简单地签署JWT密钥开发应用程序和API时,支持各种算法。在这些算法中,一些被认为是最佳实践并优于其他算法,例如带有PS256算法的椭圆曲线。某些Auth服务器使用确定性算法运行,而其他服务器使用概率性算法。一些服务器,如Auth0,不支持多个JWK,但IdentityServer4支持你配置的任意数量。该组件旨在抽象这一层并为你的应用程序提供当前JWK管理的最佳实践。
负载均衡场景
在Kubernetes或Docker Swarm中使用容器时,扩展应用程序可能会导致某些问题,例如需要将DataProtection密钥存储在集中位置。虽然不建议绕过这种情况,但使用对称密钥是一种可能的解决方案。与DataProtection类似,该组件为你的JWKS提供了集中存储。
最佳实践
许多开发人员不确定应该使用哪种算法来签署他们的JWT。默认情况下,该组件使用带有ECDSA的椭圆曲线,利用P-256和SHA-256来帮助构建更安全的API和环境。它通过提供对最佳实践的更好理解并确保使用安全算法来简化JWKS管理。
许可证
NetDevPack.Security.Jwt是开源软件,根据MIT许可证发布。该许可证允许在自由和商业应用程序及库中无限制地使用NetDevPack.Security.Jwt。