XiHan.Framework.Security.Jwt
认证的 JWT Provider:校验 HS256 对称密钥签名的 Bearer 令牌(签名、有效期、签发者、受众),把令牌映射为主体,提供 security.authentication@1。适用于令牌由应用自己的登录服务签发的场景;外部身份提供方签发的非对称令牌用 Security.OpenIdConnect。
| 项 | 内容 |
|---|---|
| 包 ID | XiHan.Framework.Security.Jwt |
| 环层 | Ring 3(实现) |
| 能力 | 提供 security.authentication@1;依赖 kernel.hosting@1 |
| 特性入口 | JwtSecurityFeature(清单 xihan.security.jwt,role: provider) |
| 配置节 | XiHan:Security:Jwt |
| 依赖 | XiHan.Framework.Security.Abstractions、Microsoft.IdentityModel.JsonWebTokens |
| AOT | 不兼容(IsAotCompatible 为 false:Microsoft.IdentityModel 未声明 AOT 兼容) |
| 稳定性 | Preview |
概述
JwtSecurityFeature 注册 JwtOptions、Bearer 方案的 JwtAuthenticationHandler 与 Authenticate 阶段的 AuthenticationMiddleware。处理器用 JsonWebTokenHandler 校验令牌,关闭入站声明映射(MapInboundClaims = false),声明按 JWT 原始类型名(sub、role、tenant)进入主体,与 ApiKey 处理器的声明形态一致。
本包只提供认证。装配后顶替 Security.InMemory 的认证部分,授权仍由被选中的授权 Provider 承担(缺省 Security.InMemory,或 Security.Database)。本包与 Security.OpenIdConnect 都提供 security.authentication@1,同一时刻只装配其一。
本包只校验令牌,不签发令牌;签发由应用的登录服务完成,签名密钥、签发者与受众与这里的配置保持一致。
何时使用
- 应用自己签发令牌、用共享的对称密钥签名:用本包。
- 令牌来自 Keycloak、Entra ID 这类外部身份提供方,签名密钥要从 JWKS 取:用 Security.OpenIdConnect。
- 发布为 Native AOT 的应用不能用本包。
安装与启用
dotnet add package XiHan.Framework.Security.Jwt --prerelease当前是预览版,安装要带 --prerelease;从源码或本地包源引用见快速上手。
using XiHan.Framework.Hosting.AspNetCore;
using XiHan.Framework.Profiles.Standard;
using XiHan.Framework.Security.Jwt;
var builder = WebApplication.CreateBuilder(args);
// Security.InMemory 让出认证,只保留授权
builder.AddXiHan(xihan => xihan
.UseStandardProfile()
.UseFeature<JwtSecurityFeature>());
var app = builder.Build();
app.UseXiHan();
app.Run();配置
键(XiHan:Security:Jwt) | 类型 | 缺省 | 说明 |
|---|---|---|---|
Secret | string | 必填 | 对称密钥,UTF-8 编码后至少 32 字节(JwtOptions.MinSecretBytes);缺失或过短在装配时抛出 ArgumentException |
Issuer | string | 不校验 | 配置了才校验签发者 |
Audience | string | 不校验 | 配置了才校验受众 |
ClockSkewSeconds | double | 60 | 有效期校验允许的时钟偏移,不能为负 |
NameClaim | string | sub | 作为主体 ID 的声明类型 |
{
"XiHan": {
"Security": {
"Jwt": {
"Secret": "dev-only-signing-key-change-me-0123456789",
"Issuer": "https://localhost:5001",
"Audience": "orders-api",
"ClockSkewSeconds": 60,
"NameClaim": "sub"
}
}
}
}密钥不要写进提交到仓库的配置文件,生产环境从环境变量或密钥库注入同名配置键。
主要类型
| 类型 | 说明 |
|---|---|
JwtSecurityFeature | 特性入口 |
JwtOptions | Secret、Issuer、Audience、ClockSkew、NameClaim;FromConfiguration 读取上表 |
JwtAuthenticationHandler | Bearer 方案的处理器;SchemeName = Bearer |
使用示例
调用方带上令牌:
curl -H "Authorization: Bearer <token>" http://localhost:5000/whoami令牌至少要有 sub(或 NameClaim 配置的类型)与 exp。要参与 RBAC 判定的角色写成类型等于 XiHan:Security:RoleClaim(缺省 role)的声明;要让 Tenant 阶段识别所属租户,写一条类型等于 XiHan:MultiTenancy:TenantClaim(缺省 tenant)的声明。显示名取 name 声明。
测试里不经宿主直接校验,调用上下文取自测试基座 XiHan.Framework.Testing:
using XiHan.Framework.Security.Abstractions.Authentication;
using XiHan.Framework.Security.Jwt;
using XiHan.Framework.Testing;
public static class TokenCheck
{
public static async Task<string?> SubjectOfAsync(string token, CancellationToken cancellationToken)
{
var handler = new JwtAuthenticationHandler(new JwtOptions { Secret = "dev-only-signing-key-change-me-0123456789", Audience = "orders-api" });
var result = await handler.AuthenticateAsync(new AuthenticationCredential(JwtAuthenticationHandler.SchemeName, token), TestInvocationContext.Create(), cancellationToken);
return result.IsSuccess ? result.ValueOrThrow().Id : null;
}
}行为与限制
- 签名、
exp与有效期始终强制校验;签发者与受众只在配置了才校验。 - 任何校验失败都返回
SECURITY_DENIED_003(消息键security.token.invalid),不区分原因对外暴露;令牌没有NameClaim声明同样返回SECURITY_DENIED_003(消息键security.token.subject_missing)。 - 经 ASP.NET Core 宿主时,无效令牌是 401 并带
WWW-Authenticate: Bearer error="invalid_token",没有令牌时是 401 并带WWW-Authenticate: Bearer(ADR-0042)。 - 容器里有
Bearer方案的处理器时,Protocols.Http发布的 OpenAPI 文档带components.securitySchemes.bearer与全局security。 - 绕过管道的端点(SignalR Hub 等)经 ASP.NET Core 方案
XiHan复用本处理器,见认证 · 绕过管道的端点。
相关
- 认证
- Security.Abstractions
- Security.OpenIdConnect
- Security.InMemory
- Hosting.AspNetCore
- 规范:RFC-0008《认证与授权》;ADR-0042、ADR-0051
