OAuth 2.0 DPoP 绑定访问Tokens
RFC 9449 OAuth 2.0 演示拥有证明 (DPoP) 是一种用于约束访问Tokens的发送方级机制。
DPoP 的主要目标是防止未经授权或非法的客户端使用泄露或被盗的访问Tokens,方法是在授权服务器颁发时将访问Tokens绑定到公钥,并要求客户端在资源服务器上使用访问Tokens时证明拥有相应的私钥。
通过 DPoP 受发送方约束的访问Tokens与典型的持有者Tokens形成鲜明对比,后者可供拥有访问Tokens的任何客户端使用。
DPoP 引入了 DPoP 证明的概念,它是由客户端创建并作为 HTTP 请求中的标头发送的 JWT。客户端使用 DPoP 证明来证明拥有与某个公密钥相对应的私钥。
当客户端发起访问Tokens请求时,它会将 DPoP 证明附加到 HTTP 标头中的请求。授权服务器将访问Tokens绑定(发送者约束)到 DPoP 证明中关联的公钥。
当客户端发起受保护的资源请求时,它会再次在 HTTP 标头中将 DPoP 证明附加到请求。
资源服务器直接在访问Tokens (JWT) 中或通过Tokens自检端点获取有关绑定到访问Tokens的公钥的信息。然后,资源服务器验证绑定到访问Tokens的公钥是否与 DPoP 证明中的公钥匹配。它还验证 DPoP 证明中的访问Tokens哈希是否与请求中的访问Tokens匹配。
DPoP 访问Tokens请求
要使用 DPoP 请求绑定到公钥的访问Tokens,客户端必须在DPoP
标头。这适用于所有访问Tokens请求,无论授权授予类型如何(例如authorization_code
,refresh_token
,client_credentials
等)。
以下 HTTP 请求显示了authorization_code
访问Tokens请求,并在DPoP
页眉:
POST /oauth2/token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
DPoP: eyJraWQiOiJyc2EtandrLWtpZCIsInR5cCI6ImRwb3Arand0IiwiYWxnIjoiUlMyNTYiLCJqd2siOnsia3R5IjoiUlNBIiwiZSI6IkFRQUIiLCJraWQiOiJyc2EtandrLWtpZCIsIm4iOiIzRmxxSnI1VFJza0lRSWdkRTNEZDdEOWxib1dkY1RVVDhhLWZKUjdNQXZRbTdYWE5vWWttM3Y3TVFMMU5ZdER2TDJsOENBbmMwV2RTVElOVTZJUnZjNUtxbzJRNGNzTlg5U0hPbUVmem9ST2pRcWFoRWN2ZTFqQlhsdW9DWGRZdVlweDRfMXRmUmdHNmlpNFVoeGg2aUk4cU5NSlFYLWZMZnFoYmZZZnhCUVZSUHl3QmtBYklQNHgxRUFzYkM2RlNObWtoQ3hpTU5xRWd4YUlwWThDMmtKZEpfWklWLVdXNG5vRGR6cEtxSGN3bUI4RnNydW1sVllfRE5WdlVTRElpcGlxOVBiUDRIOTlUWE4xbzc0Nm9SYU5hMDdycTFob0NnTVNTeS04NVNhZ0NveGxteUUtRC1vZjlTc01ZOE9sOXQwcmR6cG9iQnVoeUpfbzVkZnZqS3cifX0.eyJodG0iOiJQT1NUIiwiaHR1IjoiaHR0cHM6Ly9zZXJ2ZXIuZXhhbXBsZS5jb20vb2F1dGgyL3Rva2VuIiwiaWF0IjoxNzQ2ODA2MzA1LCJqdGkiOiI0YjIzNDBkMi1hOTFmLTQwYTUtYmFhOS1kZDRlNWRlYWM4NjcifQ.wq8gJ_G6vpiEinfaY3WhereqCCLoeJOG8tnWBBAzRWx9F1KU5yAAWq-ZVCk_k07-h6DIqz2wgv6y9dVbNpRYwNwDUeik9qLRsC60M8YW7EFVyI3n_NpujLwzZeub_nDYMVnyn4ii0NaZrYHtoGXOlswQfS_-ET-jpC0XWm5nBZsCdUEXjOYtwaACC6Js-pyNwKmSLp5SKIk11jZUR5xIIopaQy521y9qJHhGRwzj8DQGsP7wMZ98UFL0E--1c-hh4rTy8PMeWCqRHdwjj_ry_eTe0DJFcxxYQdeL7-0_0CIO4Ayx5WHEpcUOIzBRoN32RsNpDZc-5slDNj9ku004DA
grant_type=authorization_code\
&client_id=s6BhdRkqt\
&code=SplxlOBeZQQYbYS6WxSbIA\
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb\
&code_verifier=bEaL42izcC-o-xBk0K2vuJ6U-y1p9r_wW2dFWIWgjz-
下面显示了 DPoP 证明 JWT 标头和声明的表示形式:
{
"typ": "dpop+jwt",
"alg": "RS256",
"jwk": {
"kty": "RSA",
"e": "AQAB",
"n": "3FlqJr5TRskIQIgdE3Dd7D9lboWdcTUT8a-fJR7MAvQm7XXNoYkm3v7MQL1NYtDvL2l8CAnc0WdSTINU6IRvc5Kqo2Q4csNX9SHOmEfzoROjQqahEcve1jBXluoCXdYuYpx4_1tfRgG6ii4Uhxh6iI8qNMJQX-fLfqhbfYfxBQVRPywBkAbIP4x1EAsbC6FSNmkhCxiMNqEgxaIpY8C2kJdJ_ZIV-WW4noDdzpKqHcwmB8FsrumlVY_DNVvUSDIipiq9PbP4H99TXN1o746oRaNa07rq1hoCgMSSy-85SagCoxlmyE-D-of9SsMY8Ol9t0rdzpobBuhyJ_o5dfvjKw"
}
}
{
"htm": "POST",
"htu": "https://server.example.com/oauth2/token",
"iat": 1746806305,
"jti": "4b2340d2-a91f-40a5-baa9-dd4e5deac867"
}
以下代码显示了如何生成 DPoP 证明 JWT 的示例:
-
Java
RSAKey rsaKey = ...
JWKSource<SecurityContext> jwkSource = (jwkSelector, securityContext) -> jwkSelector
.select(new JWKSet(rsaKey));
NimbusJwtEncoder jwtEncoder = new NimbusJwtEncoder(jwkSource);
JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.RS256)
.type("dpop+jwt")
.jwk(rsaKey.toPublicJWK().toJSONObject())
.build();
JwtClaimsSet claims = JwtClaimsSet.builder()
.issuedAt(Instant.now())
.claim("htm", "POST")
.claim("htu", "https://server.example.com/oauth2/token")
.id(UUID.randomUUID().toString())
.build();
Jwt dPoPProof = jwtEncoder.encode(JwtEncoderParameters.from(jwsHeader, claims));
授权服务器成功验证 DPoP 证明后,DPoP 证明中的公钥将绑定(发送方约束)到颁发的访问Tokens。
以下访问Tokens响应显示了token_type
参数设置为DPoP
向客户端发出访问Tokens绑定到其 DPoP 证明公钥的信号:
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
{
"access_token": "Kz~8mXK1EalYznwH-LC-1fBAo.4Ljp~zsPE_NeO.gxU",
"token_type": "DPoP",
"expires_in": 2677
}
公钥确认
资源服务器必须能够识别访问Tokens是否与 DPoP 绑定,并验证与 DPoP 证明的公钥的绑定。绑定是通过以资源服务器可以访问的方式将公钥与访问Tokens相关联来完成的,例如直接 (JWT) 或通过Tokens自省将公钥哈希嵌入到访问Tokens中。
当访问Tokens表示为 JWT 时,公钥哈希包含在jkt
确认方式 (cnf
) 声明。
以下示例显示了包含cnf
声明jkt
声明,即 DPoP 证明公钥的 JWK SHA-256 指纹:
{
"sub":"[email protected]",
"iss":"https://server.example.com",
"nbf":1562262611,
"exp":1562266216,
"cnf":
{
"jkt":"CQMknzRoZ5YUi7vS58jck1q8TmZT8wiIiXrCN1Ny4VU"
}
}
DPoP 保护资源请求
对受 DPoP 保护的资源的请求必须同时包含 DPoP 证明和 DPoP 绑定的访问Tokens。
DPoP 证明必须包括ath
声明,其中包含访问Tokens的有效哈希值。
资源服务器将计算收到的访问Tokens的哈希值,并验证它是否与ath
DPoP 证明中的声明。
使用Authorization
身份验证方案为DPoP
.
以下 HTTP 请求显示了受保护的资源请求,其中包含 DPoP 绑定的访问TokensAuthorization
标头和 DPoP 证明DPoP
页眉:
GET /resource HTTP/1.1
Host: resource.example.com
Authorization: DPoP Kz~8mXK1EalYznwH-LC-1fBAo.4Ljp~zsPE_NeO.gxU
DPoP: eyJraWQiOiJyc2EtandrLWtpZCIsInR5cCI6ImRwb3Arand0IiwiYWxnIjoiUlMyNTYiLCJqd2siOnsia3R5IjoiUlNBIiwiZSI6IkFRQUIiLCJraWQiOiJyc2EtandrLWtpZCIsIm4iOiIzRmxxSnI1VFJza0lRSWdkRTNEZDdEOWxib1dkY1RVVDhhLWZKUjdNQXZRbTdYWE5vWWttM3Y3TVFMMU5ZdER2TDJsOENBbmMwV2RTVElOVTZJUnZjNUtxbzJRNGNzTlg5U0hPbUVmem9ST2pRcWFoRWN2ZTFqQlhsdW9DWGRZdVlweDRfMXRmUmdHNmlpNFVoeGg2aUk4cU5NSlFYLWZMZnFoYmZZZnhCUVZSUHl3QmtBYklQNHgxRUFzYkM2RlNObWtoQ3hpTU5xRWd4YUlwWThDMmtKZEpfWklWLVdXNG5vRGR6cEtxSGN3bUI4RnNydW1sVllfRE5WdlVTRElpcGlxOVBiUDRIOTlUWE4xbzc0Nm9SYU5hMDdycTFob0NnTVNTeS04NVNhZ0NveGxteUUtRC1vZjlTc01ZOE9sOXQwcmR6cG9iQnVoeUpfbzVkZnZqS3cifX0.eyJodG0iOiJHRVQiLCJodHUiOiJodHRwczovL3Jlc291cmNlLmV4YW1wbGUuY29tL3Jlc291cmNlIiwiYXRoIjoiZlVIeU8ycjJaM0RaNTNFc05yV0JiMHhXWG9hTnk1OUlpS0NBcWtzbVFFbyIsImlhdCI6MTc0NjgwNzEzOCwianRpIjoiM2MyZWU5YmItMDNhYy00MGNmLWI4MTItMDBiZmJhMzQxY2VlIn0.oS6NwjURR6wZemh1ZBNiBjycGeXwnkguLtgiKdCjQSEhFQpEJm04bBa0tdfZgWT17Z2mBgddnNQSkROzUGfssg8rBBldZXOAiduF-whtEGZA-pXXWJilXrwH3Glb6hIOMZOVmIH8fmYCDmqn-sE_DmDIsv57Il2-jdZbgeDcrxADO-6E5gsuNf1jvy7qqHq7INrKX6jRuydti_Re35lecvaAWfTyD7s7tQ_-3x_xLxxPwf_eA6z8OWbc58O2PYoUeO2JKLiOIg6UVZOZzxLEWV42WIKjha_kkoykvsf98W2y8pWOEr65u0VPsn5esw2X3I1eFL_A-XkxstZHRaGXJg
下面显示了 DPoP 证明 JWT 标头和声明的表示形式,其中包含ath
索赔:
{
"typ": "dpop+jwt",
"alg": "RS256",
"jwk": {
"kty": "RSA",
"e": "AQAB",
"n": "3FlqJr5TRskIQIgdE3Dd7D9lboWdcTUT8a-fJR7MAvQm7XXNoYkm3v7MQL1NYtDvL2l8CAnc0WdSTINU6IRvc5Kqo2Q4csNX9SHOmEfzoROjQqahEcve1jBXluoCXdYuYpx4_1tfRgG6ii4Uhxh6iI8qNMJQX-fLfqhbfYfxBQVRPywBkAbIP4x1EAsbC6FSNmkhCxiMNqEgxaIpY8C2kJdJ_ZIV-WW4noDdzpKqHcwmB8FsrumlVY_DNVvUSDIipiq9PbP4H99TXN1o746oRaNa07rq1hoCgMSSy-85SagCoxlmyE-D-of9SsMY8Ol9t0rdzpobBuhyJ_o5dfvjKw"
}
}
{
"htm": "GET",
"htu": "https://resource.example.com/resource",
"ath": "fUHyO2r2Z3DZ53EsNrWBb0xWXoaNy59IiKCAqksmQEo",
"iat": 1746807138,
"jti": "3c2ee9bb-03ac-40cf-b812-00bfba341cee"
}
以下代码显示了如何生成 DPoP 证明 JWT 的示例:
-
Java
RSAKey rsaKey = ...
JWKSource<SecurityContext> jwkSource = (jwkSelector, securityContext) -> jwkSelector
.select(new JWKSet(rsaKey));
NimbusJwtEncoder jwtEncoder = new NimbusJwtEncoder(jwkSource);
String accessToken = ...
JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.RS256)
.type("dpop+jwt")
.jwk(rsaKey.toPublicJWK().toJSONObject())
.build();
JwtClaimsSet claims = JwtClaimsSet.builder()
.issuedAt(Instant.now())
.claim("htm", "GET")
.claim("htu", "https://resource.example.com/resource")
.claim("ath", sha256(accessToken))
.id(UUID.randomUUID().toString())
.build();
Jwt dPoPProof = jwtEncoder.encode(JwtEncoderParameters.from(jwsHeader, claims));