JWT (JSON Web 令牌)持有者身份验证通常用于 API。 虽然它的工作方式与 cookie 身份验证类似,但标识提供者在身份验证成功后会发出 JWT 或令牌。 然后,这些令牌就可以发送到其他服务器进行身份验证,而不像 Cookie 那样只能发回颁发域。 JWT 是一个独立的令牌,其中封装了 API 资源或客户端的信息。 请求 JWT 的客户端可以使用授权标头和持有者令牌从 API 资源中请求数据。
JWT 持有者身份验证可提供:
-
身份验证:使用
JwtBearerHandler
时,持有者令牌对于身份验证至关重要。JwtBearerHandler
验证令牌并从其声明中提取用户的身份信息。 - 授权:持有者令牌通过提供一组表征用户或应用程序权限的声明来实现授权,这与 cookie非常相似。
- 委托授权:当使用特定于用户的访问令牌而不是应用程序范围的访问令牌在 API 之间进行身份验证时,此过程称为委托授权。
有关 JWT 持有者身份验证的简介,请参阅 JSON Web 令牌。查看或下载示例代码
本文涵盖以下几个方面:
- 令牌类型
- 使用 JWT 令牌确保 API 的安全
- OIDC/OAuth 如何适应此情况?
- 实现 JWT 持有者令牌身份验证
- 创建 JWT 的推荐方法
令牌类型
令牌的类型和格式多种多样。 除测试目的外,不建议自行生成访问令牌或 ID 令牌。 不符合既定标准的自创建令牌:
- 可能导致安全漏洞。
- 仅适用于封闭系统。
建议使用 OpenID Connect 1.0 或 OAuth 标准来创建用于 API 访问的访问令牌。
访问令牌
访问令牌:
- 是客户端应用程序用来向实现 API 的服务器发出请求的字符串。
- 格式可以不同。 不同的 API 可能使用不同的令牌格式。
- 可以加密。
- 持有访问令牌的 Web 客户端或 UI 应用不得读取或解释该访问令牌。
- 仅用于向 API 提出请求。
- 通常作为持有者令牌发送到“授权”请求标头中的 API。
请参阅 OAuth 2.0 授权框架
应用程序访问令牌和委托访问令牌
访问令牌可以是应用程序访问令牌或委托访问令牌。 这些令牌具有不同的声明,以不同的方式进行管理和存储。 应用程序访问令牌通常在应用程序中存储一次,直到过期为止,而委托访问令牌存储在每个用户的 cookie 或安全服务器缓存中。
只要涉及用户,我们都建议使用委托用户访问令牌。 下游 API 可以代表经过身份验证的用户请求一个委派用户访问令牌。
发送方受限的访问令牌
访问令牌可以作为持有者令牌或发送方受限的令牌来访问资源。 发送方受限令牌要求提出请求的客户端证明拥有私钥才能使用令牌。 证明拥有私钥可确保令牌无法被单独使用。 发送方受限的令牌可以通过两种方式实现:
ID 令牌
ID 令牌是确认用户身份验证成功的安全令牌。 这些令牌允许客户端验证用户的标识。 JWT 令牌服务器会发出包含用户信息声明的 ID 令牌。 ID 令牌始终采用 JWT 格式。
ID 令牌绝不应用于访问 API。
其他令牌
令牌有多种类型,包括 OpenID Connect 和 OAuth 标准规定的访问令牌和 ID 令牌。 刷新令牌可用于刷新 UI 应用,而无需对用户重新进行身份验证。 OAuth JAR 令牌可以安全地发送授权请求。 可验证凭据流利用 JWT 类型来签发或验证凭据。 按照规范使用令牌至关重要。 有关详细信息,请参阅本文后面提供的标准链接。
使用 JWT 令牌确保 API 的安全
当使用 JWT 访问令牌进行 API 授权时,API 会根据所提供的令牌授予或拒绝访问。 如果请求未被授权,则返回 401 或 403 响应。 API 不应将用户重定向到标识提供者,以获取新的令牌或请求其他权限。 使用 API 的应用负责获取适当的令牌。 这可确保在 API(授权)与客户端应用程序(身份验证)之间明确实现关注点分离。
注释
HTTP 还允许为未授权返回 404,以免泄露有关资源存在的信息给未经授权的客户端。
401 未授权
401 未授权响应表示所提供的访问令牌不符合规定标准。 这可能是由于多种原因,包括:
- 签名无效:令牌的签名不匹配,意味着可能存在篡改。
- 过期:令牌已过期,不再有效。
-
错误声明:令牌中的关键声明(例如受众(
aud
)或颁发者(iss
))缺失或无效。
注释
从 HTTP 语义 RFC 9110:生成 401 响应的服务器必须发送一个 WWW-Authenticate 标头字段(第 11.6.1 节),其中包含至少一个适用于目标资源的质询。
OAuth 规范提供了有关所需声明及其验证的详细指南。
403 禁止访问
403 禁止响应通常表示已通过身份验证的用户缺乏访问所请求资源的必要权限。 这与身份验证问题(如无效令牌)不同,也与访问令牌内的标准要求无关。
在 ASP.NET Core 中,可以通过授权来强制执行:
要求和策略:定义自定义要求,如“必须是管理员”,并将其与策略关联。 基于角色的授权:为用户分配角色,如“管理员”、“编辑者”,并根据这些角色限制访问权限。
使用持有者令牌时,OIDC 和/或 OAuth 具有什么作用?
当 API 使用 JWT 访问令牌进行授权时,API 只会验证访问令牌,而不会验证令牌是如何获得的。
OpenID Connect (OIDC) 和 OAuth 2.0 为令牌获取提供了标准化的安全框架。 令牌获取方式因应用类型而异。 由于安全令牌获取的复杂性,强烈建议采用这些标准:
- 对于代表用户和应用程序的应用:OIDC 是首选,可实现委托用户访问。 在 Web 应用中,建议使用具有 Proof Key for Code Exchange (PKCE) 的机密代码流来提高安全性。
- 如果调用应用是具有服务器端 OIDC 身份验证的 ASP.NET Core 应用,则可以使用 SaveTokens 选项将访问令牌存储在 cookie 中,以便稍后通过
HttpContext.GetTokenAsync("access_token")
使用。
- 如果调用应用是具有服务器端 OIDC 身份验证的 ASP.NET Core 应用,则可以使用 SaveTokens 选项将访问令牌存储在 cookie 中,以便稍后通过
- 如果应用没有用户:OAuth 2.0 客户端凭据流适用于获取应用程序访问令牌。
实现 JWT 持有者令牌身份验证
Microsoft.AspNetCore.Authentication.JwtBearer Nuget 包可用于验证 JWT 持有者令牌。
JWT 持有者令牌应在 API 中得到充分验证。 应验证以下内容:
- 签名,象征信任与诚信。 这样可以确保令牌是由指定的安全令牌服务创建的,并且没有被篡改。
- 具有预期值的颁发者声明。
- 具有预期值的受众声明。
- 令牌过期。
OAuth 2.0 访问令牌需要以下声明:iss
、exp
、aud
、sub
、client_id
、iat
和 jti
。
如果其中任何声明或值不正确,API 应返回 401 响应。
JWT 持有者令牌基本验证
AddJwtBearer 的基本实现仅能验证受众和颁发者。 签名必须经过验证,以便令牌可以被信任,并且没有被篡改。
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(jwtOptions =>
{
jwtOptions.Authority = "https://{--your-authority--}";
jwtOptions.Audience = "https://{--your-audience--}";
});
JWT 持有者令牌显式验证
AddJwtBearer 方法可提供多种配置。 某些安全令牌提供程序使用非标准元数据地址,并且可以显式设置参数。 API 可以接受多个颁发者或受众。
无需显式定义参数。 定义取决于访问令牌声明值和用于验证访问令牌的安全令牌服务器。 应尽可能使用默认值。
请参阅映射声明 MapInboundClaims 详细信息。
builder.Services.AddAuthentication()
.AddJwtBearer("some-scheme", jwtOptions =>
{
jwtOptions.MetadataAddress = builder.Configuration["Api:MetadataAddress"];
// Optional if the MetadataAddress is specified
jwtOptions.Authority = builder.Configuration["Api:Authority"];
jwtOptions.Audience = builder.Configuration["Api:Audience"];
jwtOptions.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateIssuerSigningKey = true,
ValidAudiences = builder.Configuration.GetSection("Api:ValidAudiences").Get<string[]>(),
ValidIssuers = builder.Configuration.GetSection("Api:ValidIssuers").Get<string[]>()
};
jwtOptions.MapInboundClaims = false;
});
具有多个方案的 JWT
API 通常需要支持来自各种颁发者的访问令牌。 可以通过以下方法在 API 中支持多个令牌颁发者:
- 独立的 API:为每个颁发者创建具有专用身份验证方案的独立 API。
- AddPolicyScheme此方法可定义多个身份验证方案,并实现根据令牌属性(如颁发者、声明)选择适当方案的逻辑。 此方法可让单一 API 具有更大的灵活性。
强制进行持有者身份验证
SetDefaultPolicy 可用于要求对所有请求进行身份验证,即使终结点没有 [Authorize]
属性。
SetDefaultPolicy 配置用于具有 [Authorize]
属性的终结点的策略,默认为要求已通过身份验证的用户。 有关详细信息,请参阅 要求用户进行身份验证的文档。
var requireAuthPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
builder.Services.AddAuthorizationBuilder()
.SetDefaultPolicy(requireAuthPolicy);
授权属性也可用于强制身份验证。 如果使用多个方案,一般需要将持有者方案设置为默认身份验证方案,或者通过 [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme])
指定方案。
控制器中的授权:
[Authorize]
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
最小 API 中的授权:
app.MapGet("/hello", [Authorize] () => "Hi");
创建 JWT 的推荐方法
不安全处理访问令牌,例如身份验证不严密或将令牌存储在易受攻击的客户端存储中,可能会导致严重的安全漏洞。 例如,直接在浏览器中使用本地存储、会话存储或 Web 工作器来存储访问令牌。 下一部分包含了应用使用和创建访问令牌的最佳实践。
使用标准
创建访问令牌时,请务必使用 OpenID Connect 或 OAuth 等标准。 在生产应用中不能创建访问令牌,除非遵守本文中所述的安全预防措施。 创建访问令牌应仅限于测试场景。
使用非对称密钥
创建访问令牌时,请务必使用非对称密钥。 公钥可在众所周知的终结点中获得,API 客户端可使用公钥验证访问令牌的签名。
切勿通过用户名/密码请求创建访问令牌
请勿基于用户名/密码请求创建访问令牌。 用户名/密码请求未经身份验证,容易受到模拟和网络钓鱼攻击的攻击。 只能使用 OpenID Connect 流或 OAuth 标准流来创建访问令牌。 偏离这些标准会导致应用不安全。
使用 Cookie
对于安全的 Web 应用,需要一个后端将访问令牌存储在受信任的服务器上。 客户端浏览器上仅共享一个仅限安全 HTTP 的 cookie。 有关如何在 ASP.NET Core Web 应用中执行此操作,请参阅 OIDC 身份验证文档。
下游 API
API 偶尔需要代表调用应用中经过身份验证的用户从下游 API 访问用户数据。 虽然实现 OAuth 客户端凭据流是一种选择,但这需要两个 API 应用互相完全信任。 一种更安全的方法是使用零信任策略和授权用户访问令牌。 这种方法:
- 只为特定用户授予 API 必要的权限,从而提高安全性。
- 要求 API 为调用应用和 API 的用户创建新的访问令牌。
使用委托用户访问令牌实现零信任策略有几种方法:
使用 OAuth 2.0 令牌交换请求新的委托访问令牌
这是实现这一要求的好方法,但如果必须实现 OAuth 流,则会很复杂。
请参阅 OAuth 2.0 令牌交换
使用 Microsoft Identity Web 代理流请求新的委派访问令牌
使用 Microsoft Identity Web 身份验证库是最简单、最安全的方法。 它仅适用于 Microsoft Entra ID 和 Microsoft Entra External ID。
有关详细信息,请参阅 Microsoft 标识平台和 OAuth 2.0 代理流。
使用发送到 API 的相同委托访问令牌
此方法并不难实现,但访问令牌可以访问所有下游 API。 可以使用 Yarp 反向代理来实现这一点。
使用 OAuth 客户端凭据流并使用应用程序访问令牌
这很容易实现,但客户端应用程序拥有完全的应用程序访问权限,而不是委托访问令牌。 令牌应缓存在客户端 API 应用程序中。
注释
应用间的任何安全措施也有效。 可以使用证书身份验证,或者在 Azure 中使用托管标识。
处理访问令牌
在客户端应用程序中使用访问令牌时,访问令牌需要轮换、持久化并存储在服务器上的某个地方。 在 Web 应用程序中,cookie 用于保护会话,并可通过 SaveTokens 选项存储令牌。
SaveTokens
目前不会自动刷新访问令牌,但计划在 .NET 10 中使用此功能。 请关注 https://github.com/dotnet/aspnetcore/issues/8175 以获取更新。 在此期间,可以按照 Blazor Web App 中的 OIDC 文档演示手动刷新访问令牌,或者使用 Duende.AccessTokenManagement.OpenIdConnect 等第三方 NuGet 软件包在客户端应用程序中处理和管理访问令牌。 有关详细信息,请参阅 Duende 令牌管理。
注释
如果部署到生产环境,缓存应在多实例部署中工作。 通常需要一个持久性缓存。
某些安全令牌服务器会对访问令牌进行加密。 访问令牌不需要任何格式。 使用 OAuth 自检时,将使用引用令牌,而不是访问令牌。 客户端(UI)应用程序不应打开访问令牌,因为访问令牌并非用于该目的。 只有为其创建访问令牌的 API 才能打开访问令牌。
- 不要在 UI 应用程序中打开访问令牌
- 不要向 API 发送 ID 令牌
- 访问令牌可以采用任何格式
- 访问令牌可以加密
- 访问令牌过期,需要轮换
- 访问令牌保存在安全的后端服务器上
YARP(另一种反向代理)
YARP(另一个反向代理) 是一种有用的技术,用于处理 HTTP 请求并将请求转发到其他 API。 YARP 可实现获取新访问凭据的安全逻辑。 YARP 经常用于采用前端后端(BFF)安全架构的场合。
有关 Blazor 使用 YARP 实现 BFF 模式的示例,请参阅以下文章:
Blazor有关使用 YARP 实现 BFF 模式的示例,请参阅使用 OpenID Connect 保护 ASP.NET 核心Blazor Web App(OIDC)。
有关详细信息,请参阅 auth0:Backend for Frontend 模式。
测试 API(应用程序接口)
集成测试和带有访问令牌的容器可用于测试安全的 API。 可以使用 dotnet user-jwts 工具来创建访问令牌。
警告
确保不会因测试目的而将安全问题引入 API 中。 当使用委托访问令牌时,测试会变得更具挑战性,因为这些令牌只能通过 UI 和 OpenID Connect 流创建。 如果使用测试工具创建委托访问令牌,则必须禁用安全功能进行测试。 必须确保仅在测试环境中禁用这些功能。
创建专用和隔离的测试环境,以便安全地禁用或修改安全功能。 确保这些更改仅限于测试环境。
使用 Swagger UI、Curl 和其他 API UI 工具
Swagger UI 和 Curl 是用于测试 API 的优秀 UI 工具。 为使工具发挥作用,API 可以生成 OpenAPI 文档,并将其加载到客户端测试工具中。 可以在 API OpenAPI 文件中添加获取新访问令牌的安全流。
警告
不要将不安全的安全测试流部署到生产环境中。
在为 API 实现 Swagger UI 时,通常不应将 UI 部署到生产环境中,因为必须削弱安全性才能使其正常工作。
映射 OpenID Connect 中的声明
请参阅以下文档:
标准
OAuth 2.0 emonstrating Proof of Possession (DPoP)
OAuth 2.0 JWT-Secured 授权请求 (JAR) RFC 9101
OAuth 2.0 相互 TLS 客户端身份验证和证书绑定访问令牌