mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-05-30 16:52:13 +00:00
Add documentation for DPoP support
Closes gh-17072
This commit is contained in:
parent
3110f3679a
commit
e3c39f02bc
@ -53,9 +53,14 @@ import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* An {@link AbstractHttpConfigurer} for OAuth 2.0 Demonstrating Proof of Possession
|
||||
* (DPoP) support.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 6.5
|
||||
* @see DPoPAuthenticationProvider
|
||||
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc9449">RFC 9449
|
||||
* OAuth 2.0 Demonstrating Proof of Possession (DPoP)</a>
|
||||
*/
|
||||
final class DPoPAuthenticationConfigurer<B extends HttpSecurityBuilder<B>>
|
||||
extends AbstractHttpConfigurer<DPoPAuthenticationConfigurer<B>, B> {
|
||||
|
@ -82,6 +82,7 @@
|
||||
**** xref:servlet/oauth2/resource-server/opaque-token.adoc[Opaque Token]
|
||||
**** xref:servlet/oauth2/resource-server/multitenancy.adoc[Multitenancy]
|
||||
**** xref:servlet/oauth2/resource-server/bearer-tokens.adoc[Bearer Tokens]
|
||||
**** xref:servlet/oauth2/resource-server/dpop-tokens.adoc[DPoP-bound Access Tokens]
|
||||
** xref:servlet/saml2/index.adoc[SAML2]
|
||||
*** xref:servlet/saml2/login/index.adoc[SAML2 Log In]
|
||||
**** xref:servlet/saml2/login/overview.adoc[SAML2 Log In Overview]
|
||||
|
@ -0,0 +1,212 @@
|
||||
[[oauth2-dpop-bound-access-tokens]]
|
||||
= OAuth 2.0 DPoP-bound Access Tokens
|
||||
|
||||
https://datatracker.ietf.org/doc/html/rfc9449[RFC 9449 OAuth 2.0 Demonstrating Proof of Possession (DPoP)] is an application-level mechanism for sender-constraining an access token.
|
||||
|
||||
The primary goal of DPoP is to prevent unauthorized or illegitimate clients from using leaked or stolen access tokens, by binding an access token to a public key upon issuance by the authorization server and requiring that the client proves possession of the corresponding private key when using the access token at the resource server.
|
||||
|
||||
Access tokens that are sender-constrained via DPoP stand in contrast to the typical bearer token, which can be used by any client in possession of the access token.
|
||||
|
||||
DPoP introduces the concept of a https://datatracker.ietf.org/doc/html/rfc9449#name-dpop-proof-jwts[DPoP Proof], which is a JWT created by the client and sent as a header in an HTTP request.
|
||||
A client uses a DPoP proof to prove the possession of a private key corresponding to a certain public key.
|
||||
|
||||
When the client initiates an <<dpop-access-token-request,access token request>>, it attaches a DPoP proof to the request in an HTTP header.
|
||||
The authorization server binds (sender-constrains) the access token to the public key associated in the DPoP proof.
|
||||
|
||||
When the client initiates a <<dpop-protected-resource-request,protected resource request>>, it again attaches a DPoP proof to the request in an HTTP header.
|
||||
|
||||
The resource server obtains information about the public key bound to the access token, either directly in the access token (JWT) or via the token introspection endpoint.
|
||||
The resource server then verifies that the public key bound to the access token matches the public key in the DPoP proof.
|
||||
It also verifies that the access token hash in the DPoP proof matches the access token in the request.
|
||||
|
||||
[[dpop-access-token-request]]
|
||||
== DPoP Access Token Request
|
||||
|
||||
To request an access token that is bound to a public key using DPoP, the client MUST provide a valid DPoP proof in the `DPoP` header when making an access token request to the authorization server token endpoint.
|
||||
This is applicable for all access token requests regardless of authorization grant type (e.g. `authorization_code`, `refresh_token`, `client_credentials`, etc).
|
||||
|
||||
The following HTTP request shows an `authorization_code` access token request with a DPoP proof in the `DPoP` header:
|
||||
|
||||
[source,shell]
|
||||
----
|
||||
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-
|
||||
----
|
||||
|
||||
The following shows a representation of the DPoP Proof JWT header and claims:
|
||||
|
||||
[source,json]
|
||||
----
|
||||
{
|
||||
"typ": "dpop+jwt",
|
||||
"alg": "RS256",
|
||||
"jwk": {
|
||||
"kty": "RSA",
|
||||
"e": "AQAB",
|
||||
"n": "3FlqJr5TRskIQIgdE3Dd7D9lboWdcTUT8a-fJR7MAvQm7XXNoYkm3v7MQL1NYtDvL2l8CAnc0WdSTINU6IRvc5Kqo2Q4csNX9SHOmEfzoROjQqahEcve1jBXluoCXdYuYpx4_1tfRgG6ii4Uhxh6iI8qNMJQX-fLfqhbfYfxBQVRPywBkAbIP4x1EAsbC6FSNmkhCxiMNqEgxaIpY8C2kJdJ_ZIV-WW4noDdzpKqHcwmB8FsrumlVY_DNVvUSDIipiq9PbP4H99TXN1o746oRaNa07rq1hoCgMSSy-85SagCoxlmyE-D-of9SsMY8Ol9t0rdzpobBuhyJ_o5dfvjKw"
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[source,json]
|
||||
----
|
||||
{
|
||||
"htm": "POST",
|
||||
"htu": "https://server.example.com/oauth2/token",
|
||||
"iat": 1746806305,
|
||||
"jti": "4b2340d2-a91f-40a5-baa9-dd4e5deac867"
|
||||
}
|
||||
----
|
||||
|
||||
The following code shows an example of how to generate the DPoP Proof JWT:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
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));
|
||||
----
|
||||
======
|
||||
|
||||
After the authorization server successfully validates the DPoP proof, the public key from the DPoP proof will be bound (sender-constrained) to the issued access token.
|
||||
|
||||
The following access token response shows the `token_type` parameter as `DPoP` to signal to the client that the access token was bound to its DPoP proof public key:
|
||||
|
||||
[source,shell]
|
||||
----
|
||||
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
|
||||
}
|
||||
----
|
||||
|
||||
[[dpop-public-key-confirmation]]
|
||||
== Public Key Confirmation
|
||||
|
||||
Resource servers MUST be able to identify whether an access token is DPoP-bound and verify the binding to the public key of the DPoP proof.
|
||||
The binding is accomplished by associating the public key with the access token in a way that can be accessed by the resource server, such as embedding the public key hash in the access token directly (JWT) or through token introspection.
|
||||
|
||||
When an access token is represented as a JWT, the public key hash is contained in the `jkt` claim under the confirmation method (`cnf`) claim.
|
||||
|
||||
The following example shows the claims of a JWT access token containing a `cnf` claim with a `jkt` claim, which is the JWK SHA-256 Thumbprint of the DPoP proof public key:
|
||||
|
||||
[source,json]
|
||||
----
|
||||
{
|
||||
"sub":"user@example.com",
|
||||
"iss":"https://server.example.com",
|
||||
"nbf":1562262611,
|
||||
"exp":1562266216,
|
||||
"cnf":
|
||||
{
|
||||
"jkt":"CQMknzRoZ5YUi7vS58jck1q8TmZT8wiIiXrCN1Ny4VU"
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[[dpop-protected-resource-request]]
|
||||
== DPoP Protected Resource Request
|
||||
|
||||
Requests to DPoP-protected resources MUST include both a DPoP proof and the DPoP-bound access token.
|
||||
The DPoP proof MUST include the `ath` claim with a valid hash of the access token.
|
||||
The resource server will calculate the hash of the received access token and verify that it is the same as the `ath` claim in the DPoP proof.
|
||||
|
||||
A DPoP-bound access token is sent using the `Authorization` request header with an authentication scheme of `DPoP`.
|
||||
|
||||
The following HTTP request shows a protected resource request with a DPoP-bound access token in the `Authorization` header and the DPoP proof in the `DPoP` header:
|
||||
|
||||
[source,shell]
|
||||
----
|
||||
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
|
||||
----
|
||||
|
||||
The following shows a representation of the DPoP Proof JWT header and claims with the `ath` claim:
|
||||
|
||||
[source,json]
|
||||
----
|
||||
{
|
||||
"typ": "dpop+jwt",
|
||||
"alg": "RS256",
|
||||
"jwk": {
|
||||
"kty": "RSA",
|
||||
"e": "AQAB",
|
||||
"n": "3FlqJr5TRskIQIgdE3Dd7D9lboWdcTUT8a-fJR7MAvQm7XXNoYkm3v7MQL1NYtDvL2l8CAnc0WdSTINU6IRvc5Kqo2Q4csNX9SHOmEfzoROjQqahEcve1jBXluoCXdYuYpx4_1tfRgG6ii4Uhxh6iI8qNMJQX-fLfqhbfYfxBQVRPywBkAbIP4x1EAsbC6FSNmkhCxiMNqEgxaIpY8C2kJdJ_ZIV-WW4noDdzpKqHcwmB8FsrumlVY_DNVvUSDIipiq9PbP4H99TXN1o746oRaNa07rq1hoCgMSSy-85SagCoxlmyE-D-of9SsMY8Ol9t0rdzpobBuhyJ_o5dfvjKw"
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[source,json]
|
||||
----
|
||||
{
|
||||
"htm": "GET",
|
||||
"htu": "https://resource.example.com/resource",
|
||||
"ath": "fUHyO2r2Z3DZ53EsNrWBb0xWXoaNy59IiKCAqksmQEo",
|
||||
"iat": 1746807138,
|
||||
"jti": "3c2ee9bb-03ac-40cf-b812-00bfba341cee"
|
||||
}
|
||||
----
|
||||
|
||||
The following code shows an example of how to generate the DPoP Proof JWT:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
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));
|
||||
----
|
||||
======
|
@ -7,6 +7,7 @@ Below are the highlights of the release, or you can view https://github.com/spri
|
||||
== New Features
|
||||
|
||||
* Support for automatic context-propagation with Micrometer (https://github.com/spring-projects/spring-security/issues/16665[gh-16665])
|
||||
* OAuth 2.0 Demonstrating Proof of Possession (DPoP) (https://github.com/spring-projects/spring-security/pull/16574[gh-16574])
|
||||
|
||||
== Breaking Changes
|
||||
|
||||
|
@ -23,6 +23,9 @@ import org.springframework.security.oauth2.core.OAuth2Token;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A context class that holds a DPoP Proof {@link Jwt} and additional parameters
|
||||
* associated to an Access Token request or a Protected Resource request.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 6.5
|
||||
* @see DPoPProofJwtDecoderFactory
|
||||
@ -44,28 +47,58 @@ public final class DPoPProofContext {
|
||||
this.accessToken = accessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the DPoP Proof {@link Jwt}.
|
||||
* @return the DPoP Proof {@link Jwt}
|
||||
*/
|
||||
public String getDPoPProof() {
|
||||
return this.dPoPProof;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the HTTP method of the request to which the DPoP Proof
|
||||
* {@link Jwt} is attached.
|
||||
* @return the value of the HTTP method of the request to which the DPoP Proof
|
||||
* {@link Jwt} is attached
|
||||
*/
|
||||
public String getMethod() {
|
||||
return this.method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the HTTP target URI of the request to which the DPoP Proof
|
||||
* {@link Jwt} is attached, without query and fragment parts.
|
||||
* @return the value of the HTTP target URI of the request to which the DPoP Proof
|
||||
* {@link Jwt} is attached
|
||||
*/
|
||||
public String getTargetUri() {
|
||||
return this.targetUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the access token if the request is a Protected Resource request.
|
||||
* @param <T> the type of the access token
|
||||
* @return the access token if the request is a Protected Resource request or
|
||||
* {@code null}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
public <T extends OAuth2Token> T getAccessToken() {
|
||||
return (T) this.accessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link Builder}, initialized with the DPoP Proof {@link Jwt}.
|
||||
* @param dPoPProof the DPoP Proof {@link Jwt}
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public static Builder withDPoPProof(String dPoPProof) {
|
||||
return new Builder(dPoPProof);
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for {@link DPoPProofContext}.
|
||||
*/
|
||||
public static final class Builder {
|
||||
|
||||
private String dPoPProof;
|
||||
@ -81,21 +114,45 @@ public final class DPoPProofContext {
|
||||
this.dPoPProof = dPoPProof;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the HTTP method of the request to which the DPoP Proof
|
||||
* {@link Jwt} is attached.
|
||||
* @param method the value of the HTTP method of the request to which the DPoP
|
||||
* Proof {@link Jwt} is attached
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder method(String method) {
|
||||
this.method = method;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the HTTP target URI of the request to which the DPoP Proof
|
||||
* {@link Jwt} is attached, without query and fragment parts.
|
||||
* @param targetUri the value of the HTTP target URI of the request to which the
|
||||
* DPoP Proof {@link Jwt} is attached
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder targetUri(String targetUri) {
|
||||
this.targetUri = targetUri;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the access token if the request is a Protected Resource request.
|
||||
* @param accessToken the access token if the request is a Protected Resource
|
||||
* request
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder accessToken(OAuth2Token accessToken) {
|
||||
this.accessToken = accessToken;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new {@link DPoPProofContext}.
|
||||
* @return a {@link DPoPProofContext}
|
||||
*/
|
||||
public DPoPProofContext build() {
|
||||
validate();
|
||||
return new DPoPProofContext(this.dPoPProof, this.method, this.targetUri, this.accessToken);
|
||||
|
@ -48,17 +48,29 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A {@link JwtDecoderFactory factory} that provides a {@link JwtDecoder} for the
|
||||
* specified {@link DPoPProofContext} and is used for authenticating a DPoP Proof
|
||||
* {@link Jwt}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 6.5
|
||||
* @see JwtDecoderFactory
|
||||
* @see DPoPProofContext
|
||||
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc9449">RFC 9449
|
||||
* OAuth 2.0 Demonstrating Proof of Possession (DPoP)</a>
|
||||
*/
|
||||
public final class DPoPProofJwtDecoderFactory implements JwtDecoderFactory<DPoPProofContext> {
|
||||
|
||||
/**
|
||||
* The default {@code OAuth2TokenValidator<Jwt>} factory that validates the
|
||||
* {@code htm}, {@code htu}, {@code jti} and {@code iat} claims of the DPoP Proof
|
||||
* {@link Jwt}.
|
||||
*/
|
||||
public static final Function<DPoPProofContext, OAuth2TokenValidator<Jwt>> DEFAULT_JWT_VALIDATOR_FACTORY = defaultJwtValidatorFactory();
|
||||
|
||||
private static final JOSEObjectTypeVerifier<SecurityContext> DPOP_TYPE_VERIFIER = new DefaultJOSEObjectTypeVerifier<>(
|
||||
new JOSEObjectType("dpop+jwt"));
|
||||
|
||||
public static final Function<DPoPProofContext, OAuth2TokenValidator<Jwt>> DEFAULT_JWT_VALIDATOR_FACTORY = defaultJwtValidatorFactory();
|
||||
|
||||
private Function<DPoPProofContext, OAuth2TokenValidator<Jwt>> jwtValidatorFactory = DEFAULT_JWT_VALIDATOR_FACTORY;
|
||||
|
||||
@Override
|
||||
@ -69,6 +81,14 @@ public final class DPoPProofJwtDecoderFactory implements JwtDecoderFactory<DPoPP
|
||||
return jwtDecoder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the factory that provides an {@link OAuth2TokenValidator} for the specified
|
||||
* {@link DPoPProofContext} and is used by the {@link JwtDecoder}. The default
|
||||
* {@code OAuth2TokenValidator<Jwt>} factory is
|
||||
* {@link #DEFAULT_JWT_VALIDATOR_FACTORY}.
|
||||
* @param jwtValidatorFactory the factory that provides an
|
||||
* {@link OAuth2TokenValidator} for the specified {@link DPoPProofContext}
|
||||
*/
|
||||
public void setJwtValidatorFactory(Function<DPoPProofContext, OAuth2TokenValidator<Jwt>> jwtValidatorFactory) {
|
||||
Assert.notNull(jwtValidatorFactory, "jwtValidatorFactory cannot be null");
|
||||
this.jwtValidatorFactory = jwtValidatorFactory;
|
||||
|
@ -50,10 +50,15 @@ import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* An {@link AuthenticationProvider} implementation that is responsible for authenticating
|
||||
* a DPoP-bound access token for a protected resource request.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 6.5
|
||||
* @see DPoPAuthenticationToken
|
||||
* @see DPoPProofJwtDecoderFactory
|
||||
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc9449">RFC 9449
|
||||
* OAuth 2.0 Demonstrating Proof of Possession (DPoP)</a>
|
||||
*/
|
||||
public final class DPoPAuthenticationProvider implements AuthenticationProvider {
|
||||
|
||||
@ -61,6 +66,11 @@ public final class DPoPAuthenticationProvider implements AuthenticationProvider
|
||||
|
||||
private JwtDecoderFactory<DPoPProofContext> dPoPProofVerifierFactory;
|
||||
|
||||
/**
|
||||
* Constructs a {@code DPoPAuthenticationProvider} using the provided parameters.
|
||||
* @param tokenAuthenticationManager the {@link AuthenticationManager} used to
|
||||
* authenticate the DPoP-bound access token
|
||||
*/
|
||||
public DPoPAuthenticationProvider(AuthenticationManager tokenAuthenticationManager) {
|
||||
Assert.notNull(tokenAuthenticationManager, "tokenAuthenticationManager cannot be null");
|
||||
this.tokenAuthenticationManager = tokenAuthenticationManager;
|
||||
@ -121,6 +131,13 @@ public final class DPoPAuthenticationProvider implements AuthenticationProvider
|
||||
return DPoPAuthenticationToken.class.isAssignableFrom(authentication);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link JwtDecoderFactory} that provides a {@link JwtDecoder} for the
|
||||
* specified {@link DPoPProofContext} and is used for authenticating a DPoP Proof
|
||||
* {@link Jwt}. The default factory is {@link DPoPProofJwtDecoderFactory}.
|
||||
* @param dPoPProofVerifierFactory the {@link JwtDecoderFactory} that provides a
|
||||
* {@link JwtDecoder} for the specified {@link DPoPProofContext}
|
||||
*/
|
||||
public void setDPoPProofVerifierFactory(JwtDecoderFactory<DPoPProofContext> dPoPProofVerifierFactory) {
|
||||
Assert.notNull(dPoPProofVerifierFactory, "dPoPProofVerifierFactory cannot be null");
|
||||
this.dPoPProofVerifierFactory = dPoPProofVerifierFactory;
|
||||
|
@ -20,9 +20,14 @@ import java.io.Serial;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An {@link Authentication} representing a protected resource request with a DPoP-bound
|
||||
* access token.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 6.5
|
||||
* @see DPoPAuthenticationProvider
|
||||
@ -40,6 +45,14 @@ public class DPoPAuthenticationToken extends AbstractAuthenticationToken {
|
||||
|
||||
private final String resourceUri;
|
||||
|
||||
/**
|
||||
* Constructs a {@code DPoPAuthenticationToken} using the provided parameters.
|
||||
* @param accessToken the DPoP-bound access token
|
||||
* @param dPoPProof the DPoP Proof {@link Jwt}
|
||||
* @param method the value of the HTTP method of the request
|
||||
* @param resourceUri the value of the HTTP resource URI of the request, without query
|
||||
* and fragment parts
|
||||
*/
|
||||
public DPoPAuthenticationToken(String accessToken, String dPoPProof, String method, String resourceUri) {
|
||||
super(Collections.emptyList());
|
||||
Assert.hasText(accessToken, "accessToken cannot be empty");
|
||||
@ -62,18 +75,35 @@ public class DPoPAuthenticationToken extends AbstractAuthenticationToken {
|
||||
return getAccessToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the DPoP-bound access token.
|
||||
* @return the DPoP-bound access token
|
||||
*/
|
||||
public String getAccessToken() {
|
||||
return this.accessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the DPoP Proof {@link Jwt}.
|
||||
* @return the DPoP Proof {@link Jwt}
|
||||
*/
|
||||
public String getDPoPProof() {
|
||||
return this.dPoPProof;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the HTTP method of the request.
|
||||
* @return the value of the HTTP method of the request
|
||||
*/
|
||||
public String getMethod() {
|
||||
return this.method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the HTTP resource URI of the request, without query and
|
||||
* fragment parts.
|
||||
* @return the value of the HTTP resource URI of the request
|
||||
*/
|
||||
public String getResourceUri() {
|
||||
return this.resourceUri;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user