diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/AbstractOAuth2AuthorizationServerMetadata.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/AbstractOAuth2AuthorizationServerMetadata.java
index 1c382949..677ce611 100644
--- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/AbstractOAuth2AuthorizationServerMetadata.java
+++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/AbstractOAuth2AuthorizationServerMetadata.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2020-2023 the original author or authors.
+ * Copyright 2020-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,11 +34,13 @@ import org.springframework.util.Assert;
* The metadata endpoint returns a set of claims an Authorization Server describes about its configuration.
*
* @author Daniel Garnier-Moiroux
+ * @author Joe Grandja
* @see OAuth2AuthorizationServerMetadataClaimAccessor
* @since 0.1.1
* @see 3.2. Authorization Server Metadata Response
* @see 4.2. OpenID Provider Configuration Response
* @see 4. Device Authorization Grant Metadata
+ * @see 3.3 Mutual-TLS Client Certificate-Bound Access Tokens Metadata
*/
public abstract class AbstractOAuth2AuthorizationServerMetadata implements OAuth2AuthorizationServerMetadataClaimAccessor, Serializable {
private static final long serialVersionUID = SpringAuthorizationServerVersion.SERIAL_VERSION_UID;
@@ -320,6 +322,17 @@ public abstract class AbstractOAuth2AuthorizationServerMetadata implements OAuth
return getThis();
}
+ /**
+ * Use this {@code tls_client_certificate_bound_access_tokens} in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL.
+ *
+ * @param tlsClientCertificateBoundAccessTokens {@code true} to indicate support for mutual-TLS client certificate-bound access tokens
+ * @return the {@link AbstractBuilder} for further configuration
+ * @since 1.3
+ */
+ public B tlsClientCertificateBoundAccessTokens(boolean tlsClientCertificateBoundAccessTokens) {
+ return claim(OAuth2AuthorizationServerMetadataClaimNames.TLS_CLIENT_CERTIFICATE_BOUND_ACCESS_TOKENS, tlsClientCertificateBoundAccessTokens);
+ }
+
/**
* Use this claim in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}.
*
diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataClaimAccessor.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataClaimAccessor.java
index 089944d1..d2ce46aa 100644
--- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataClaimAccessor.java
+++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataClaimAccessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2020-2023 the original author or authors.
+ * Copyright 2020-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,12 +25,14 @@ import org.springframework.security.oauth2.core.ClaimAccessor;
* used in OAuth 2.0 Authorization Server Metadata and OpenID Connect Discovery 1.0.
*
* @author Daniel Garnier-Moiroux
+ * @author Joe Grandja
* @since 0.1.1
* @see ClaimAccessor
* @see OAuth2AuthorizationServerMetadataClaimNames
* @see 2. Authorization Server Metadata
* @see 3. OpenID Provider Metadata
* @see 4. Device Authorization Grant Metadata
+ * @see 3.3 Mutual-TLS Client Certificate-Bound Access Tokens Metadata
*/
public interface OAuth2AuthorizationServerMetadataClaimAccessor extends ClaimAccessor {
@@ -171,4 +173,14 @@ public interface OAuth2AuthorizationServerMetadataClaimAccessor extends ClaimAcc
return getClaimAsStringList(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED);
}
+ /**
+ * Returns {@code true} to indicate support for mutual-TLS client certificate-bound access tokens {@code (tls_client_certificate_bound_access_tokens)}.
+ *
+ * @return {@code true} to indicate support for mutual-TLS client certificate-bound access tokens, {@code false} otherwise
+ * @since 1.3
+ */
+ default boolean isTlsClientCertificateBoundAccessTokens() {
+ return Boolean.TRUE.equals(getClaimAsBoolean(OAuth2AuthorizationServerMetadataClaimNames.TLS_CLIENT_CERTIFICATE_BOUND_ACCESS_TOKENS));
+ }
+
}
diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataClaimNames.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataClaimNames.java
index 831e0ec7..f2decf90 100644
--- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataClaimNames.java
+++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataClaimNames.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2020-2023 the original author or authors.
+ * Copyright 2020-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,10 +20,12 @@ package org.springframework.security.oauth2.server.authorization;
* used in OAuth 2.0 Authorization Server Metadata and OpenID Connect Discovery 1.0.
*
* @author Daniel Garnier-Moiroux
+ * @author Joe Grandja
* @since 0.1.1
* @see 2. Authorization Server Metadata
* @see 3. OpenID Provider Metadata
* @see 4. Device Authorization Grant Metadata
+ * @see 3.3 Mutual-TLS Client Certificate-Bound Access Tokens Metadata
*/
public class OAuth2AuthorizationServerMetadataClaimNames {
@@ -104,6 +106,12 @@ public class OAuth2AuthorizationServerMetadataClaimNames {
*/
public static final String CODE_CHALLENGE_METHODS_SUPPORTED = "code_challenge_methods_supported";
+ /**
+ * {@code tls_client_certificate_bound_access_tokens} - {@code true} to indicate support for mutual-TLS client certificate-bound access tokens
+ * @since 1.3
+ */
+ public static final String TLS_CLIENT_CERTIFICATE_BOUND_ACCESS_TOKENS = "tls_client_certificate_bound_access_tokens";
+
protected OAuth2AuthorizationServerMetadataClaimNames() {
}
diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilter.java
index 70639cee..41aa8373 100644
--- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilter.java
+++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilter.java
@@ -111,6 +111,7 @@ public final class OidcProviderConfigurationEndpointFilter extends OncePerReques
.tokenIntrospectionEndpoint(asUrl(issuer, authorizationServerSettings.getTokenIntrospectionEndpoint()))
.tokenIntrospectionEndpointAuthenticationMethods(clientAuthenticationMethods())
.codeChallengeMethod("S256")
+ .tlsClientCertificateBoundAccessTokens(true)
.subjectType("public")
.idTokenSigningAlgorithm(SignatureAlgorithm.RS256.getName())
.scope(OidcScopes.OPENID);
diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/ConfigurationSettingNames.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/ConfigurationSettingNames.java
index 5b35f5b3..27f5c609 100644
--- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/ConfigurationSettingNames.java
+++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/ConfigurationSettingNames.java
@@ -189,6 +189,13 @@ public final class ConfigurationSettingNames {
*/
public static final String ID_TOKEN_SIGNATURE_ALGORITHM = TOKEN_SETTINGS_NAMESPACE.concat("id-token-signature-algorithm");
+ /**
+ * Set to {@code true} if access tokens must be bound to the client {@code X509Certificate}
+ * received during client authentication when using the {@code tls_client_auth} or {@code self_signed_tls_client_auth} method.
+ * @since 1.3
+ */
+ public static final String X509_CERTIFICATE_BOUND_ACCESS_TOKENS = TOKEN_SETTINGS_NAMESPACE.concat("x509-certificate-bound-access-tokens");
+
private Token() {
}
diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/TokenSettings.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/TokenSettings.java
index 2cbb024c..c222cb1f 100644
--- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/TokenSettings.java
+++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/TokenSettings.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2020-2023 the original author or authors.
+ * Copyright 2020-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -103,6 +103,18 @@ public final class TokenSettings extends AbstractSettings {
return getSetting(ConfigurationSettingNames.Token.ID_TOKEN_SIGNATURE_ALGORITHM);
}
+ /**
+ * Returns {@code true} if access tokens must be bound to the client {@code X509Certificate}
+ * received during client authentication when using the {@code tls_client_auth} or {@code self_signed_tls_client_auth} method.
+ * The default is {@code false}.
+ *
+ * @return {@code true} if access tokens must be bound to the client {@code X509Certificate}, {@code false} otherwise
+ * @since 1.3
+ */
+ public boolean isX509CertificateBoundAccessTokens() {
+ return getSetting(ConfigurationSettingNames.Token.X509_CERTIFICATE_BOUND_ACCESS_TOKENS);
+ }
+
/**
* Constructs a new {@link Builder} with the default settings.
*
@@ -116,7 +128,8 @@ public final class TokenSettings extends AbstractSettings {
.deviceCodeTimeToLive(Duration.ofMinutes(5))
.reuseRefreshTokens(true)
.refreshTokenTimeToLive(Duration.ofMinutes(60))
- .idTokenSignatureAlgorithm(SignatureAlgorithm.RS256);
+ .idTokenSignatureAlgorithm(SignatureAlgorithm.RS256)
+ .x509CertificateBoundAccessTokens(false);
}
/**
@@ -224,6 +237,18 @@ public final class TokenSettings extends AbstractSettings {
return setting(ConfigurationSettingNames.Token.ID_TOKEN_SIGNATURE_ALGORITHM, idTokenSignatureAlgorithm);
}
+ /**
+ * Set to {@code true} if access tokens must be bound to the client {@code X509Certificate}
+ * received during client authentication when using the {@code tls_client_auth} or {@code self_signed_tls_client_auth} method.
+ *
+ * @param x509CertificateBoundAccessTokens {@code true} if access tokens must be bound to the client {@code X509Certificate}, {@code false} otherwise
+ * @return the {@link Builder} for further configuration
+ * @since 1.3
+ */
+ public Builder x509CertificateBoundAccessTokens(boolean x509CertificateBoundAccessTokens) {
+ return setting(ConfigurationSettingNames.Token.X509_CERTIFICATE_BOUND_ACCESS_TOKENS, x509CertificateBoundAccessTokens);
+ }
+
/**
* Builds the {@link TokenSettings}.
*
diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/DefaultOAuth2TokenClaimsConsumer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/DefaultOAuth2TokenClaimsConsumer.java
new file mode 100644
index 00000000..98edcc2a
--- /dev/null
+++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/DefaultOAuth2TokenClaimsConsumer.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2020-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.oauth2.server.authorization.token;
+
+import java.security.MessageDigest;
+import java.security.cert.X509Certificate;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+
+import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.core.OAuth2Error;
+import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
+import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
+
+/**
+ * @author Joe Grandja
+ * @since 1.3
+ */
+final class DefaultOAuth2TokenClaimsConsumer implements Consumer