浏览代码

Remove CryptoKeySource

Closes gh-196
Joe Grandja 4 年之前
父节点
当前提交
12f4001c9d
共有 34 个文件被更改,包括 1083 次插入1274 次删除
  1. 48 36
      oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerConfigurer.java
  2. 0 78
      oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/AsymmetricKey.java
  3. 0 231
      oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/CryptoKey.java
  4. 0 38
      oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/CryptoKeySource.java
  5. 0 62
      oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/StaticKeyGeneratingCryptoKeySource.java
  6. 0 61
      oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/SymmetricKey.java
  7. 47 46
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/jwt/JoseHeader.java
  8. 17 14
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/jwt/JoseHeaderNames.java
  9. 13 21
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/jwt/JwtClaimsSet.java
  10. 1 3
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/jwt/JwtEncoder.java
  11. 137 139
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwsEncoder.java
  12. 2 2
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIssuerUtil.java
  13. 35 66
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/NimbusJwkSetEndpointFilter.java
  14. 19 15
      oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationCodeGrantTests.java
  15. 15 11
      oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ClientCredentialsGrantTests.java
  16. 15 11
      oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2RefreshTokenGrantTests.java
  17. 15 11
      oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2TokenRevocationTests.java
  18. 16 12
      oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcTests.java
  19. 0 113
      oauth2-authorization-server/src/test/java/org/springframework/security/crypto/key/CryptoKeyTests.java
  20. 0 42
      oauth2-authorization-server/src/test/java/org/springframework/security/crypto/key/TestCryptoKeys.java
  21. 89 0
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/jose/TestJwks.java
  22. 154 0
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/jose/TestKeys.java
  23. 0 177
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/jose/jws/NimbusJwsEncoderTests.java
  24. 5 5
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/jwt/JoseHeaderTests.java
  25. 291 0
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwsEncoderTests.java
  26. 11 9
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/jwt/TestJoseHeaders.java
  27. 8 4
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/jwt/TestJwtClaimsSets.java
  28. 2 2
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProviderTests.java
  29. 2 2
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationProviderTests.java
  30. 2 2
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProviderTests.java
  31. 43 49
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/NimbusJwkSetEndpointFilterTests.java
  32. 13 7
      samples/boot/oauth2-integration/authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java
  33. 74 0
      samples/boot/oauth2-integration/authorizationserver/src/main/java/sample/jose/Jwks.java
  34. 9 5
      samples/boot/oauth2-integration/authorizationserver/src/main/java/sample/jose/KeyGeneratorUtils.java

+ 48 - 36
oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerConfigurer.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 the original author or authors.
+ * Copyright 2020-2021 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.
@@ -15,18 +15,28 @@
  */
 package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
 
+import java.net.URI;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.nimbusds.jose.jwk.source.JWKSource;
+import com.nimbusds.jose.proc.SecurityContext;
+
 import org.springframework.beans.factory.BeanFactoryUtils;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
 import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
 import org.springframework.context.ApplicationContext;
+import org.springframework.core.ResolvableType;
 import org.springframework.http.HttpMethod;
 import org.springframework.http.HttpStatus;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
 import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
 import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer;
-import org.springframework.security.crypto.key.CryptoKeySource;
-import org.springframework.security.oauth2.jose.jws.NimbusJwsEncoder;
 import org.springframework.security.oauth2.jwt.JwtEncoder;
+import org.springframework.security.oauth2.jwt.NimbusJwsEncoder;
 import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService;
 import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
 import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationProvider;
@@ -36,12 +46,12 @@ import org.springframework.security.oauth2.server.authorization.authentication.O
 import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationProvider;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
 import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
-import org.springframework.security.oauth2.server.authorization.web.JwkSetEndpointFilter;
+import org.springframework.security.oauth2.server.authorization.oidc.web.OidcProviderConfigurationEndpointFilter;
+import org.springframework.security.oauth2.server.authorization.web.NimbusJwkSetEndpointFilter;
 import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter;
 import org.springframework.security.oauth2.server.authorization.web.OAuth2ClientAuthenticationFilter;
 import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter;
 import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenRevocationEndpointFilter;
-import org.springframework.security.oauth2.server.authorization.oidc.web.OidcProviderConfigurationEndpointFilter;
 import org.springframework.security.web.AuthenticationEntryPoint;
 import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
 import org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint;
@@ -55,12 +65,6 @@ import org.springframework.security.web.util.matcher.RequestMatcher;
 import org.springframework.util.Assert;
 import org.springframework.util.StringUtils;
 
-import java.net.URI;
-import java.util.Arrays;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
 /**
  * An {@link AbstractHttpConfigurer} for OAuth 2.0 Authorization Server support.
  *
@@ -73,7 +77,7 @@ import java.util.Map;
  * @see OAuth2AuthorizationEndpointFilter
  * @see OAuth2TokenEndpointFilter
  * @see OAuth2TokenRevocationEndpointFilter
- * @see JwkSetEndpointFilter
+ * @see NimbusJwkSetEndpointFilter
  * @see OidcProviderConfigurationEndpointFilter
  * @see OAuth2ClientAuthenticationFilter
  */
@@ -92,7 +96,7 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
 	private final RequestMatcher tokenRevocationEndpointMatcher = new AntPathRequestMatcher(
 			OAuth2TokenRevocationEndpointFilter.DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI, HttpMethod.POST.name());
 	private final RequestMatcher jwkSetEndpointMatcher = new AntPathRequestMatcher(
-			JwkSetEndpointFilter.DEFAULT_JWK_SET_ENDPOINT_URI, HttpMethod.GET.name());
+			NimbusJwkSetEndpointFilter.DEFAULT_JWK_SET_ENDPOINT_URI, HttpMethod.GET.name());
 	private final RequestMatcher oidcProviderConfigurationEndpointMatcher = new AntPathRequestMatcher(
 			OidcProviderConfigurationEndpointFilter.DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI, HttpMethod.GET.name());
 
@@ -120,18 +124,6 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
 		return this;
 	}
 
-	/**
-	 * Sets the source for cryptographic keys.
-	 *
-	 * @param keySource the source for cryptographic keys
-	 * @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
-	 */
-	public OAuth2AuthorizationServerConfigurer<B> keySource(CryptoKeySource keySource) {
-		Assert.notNull(keySource, "keySource cannot be null");
-		this.getBuilder().setSharedObject(CryptoKeySource.class, keySource);
-		return this;
-	}
-
 	/**
 	 * Sets the provider settings.
 	 *
@@ -219,8 +211,9 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
 			builder.addFilterBefore(postProcess(oidcProviderConfigurationEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
 		}
 
-		JwkSetEndpointFilter jwkSetEndpointFilter = new JwkSetEndpointFilter(
-				getKeySource(builder),
+		JWKSource<SecurityContext> jwkSource = getJwkSource(builder);
+		NimbusJwkSetEndpointFilter jwkSetEndpointFilter = new NimbusJwkSetEndpointFilter(
+				jwkSource,
 				providerSettings.jwkSetEndpoint());
 		builder.addFilterBefore(postProcess(jwkSetEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
 
@@ -284,21 +277,27 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
 	}
 
 	private static <B extends HttpSecurityBuilder<B>> JwtEncoder getJwtEncoder(B builder) {
-		JwtEncoder jwtEncoder = getOptionalBean(builder, JwtEncoder.class);
+		JwtEncoder jwtEncoder = builder.getSharedObject(JwtEncoder.class);
 		if (jwtEncoder == null) {
-			CryptoKeySource keySource = getKeySource(builder);
-			jwtEncoder = new NimbusJwsEncoder(keySource);
+			jwtEncoder = getOptionalBean(builder, JwtEncoder.class);
+			if (jwtEncoder == null) {
+				JWKSource<SecurityContext> jwkSource = getJwkSource(builder);
+				jwtEncoder = new NimbusJwsEncoder(jwkSource);
+			}
+			builder.setSharedObject(JwtEncoder.class, jwtEncoder);
 		}
 		return jwtEncoder;
 	}
 
-	private static <B extends HttpSecurityBuilder<B>> CryptoKeySource getKeySource(B builder) {
-		CryptoKeySource keySource = builder.getSharedObject(CryptoKeySource.class);
-		if (keySource == null) {
-			keySource = getBean(builder, CryptoKeySource.class);
-			builder.setSharedObject(CryptoKeySource.class, keySource);
+	@SuppressWarnings("unchecked")
+	private static <B extends HttpSecurityBuilder<B>> JWKSource<SecurityContext> getJwkSource(B builder) {
+		JWKSource<SecurityContext> jwkSource = builder.getSharedObject(JWKSource.class);
+		if (jwkSource == null) {
+			ResolvableType type = ResolvableType.forClassWithGenerics(JWKSource.class, SecurityContext.class);
+			jwkSource = getBean(builder, type);
+			builder.setSharedObject(JWKSource.class, jwkSource);
 		}
-		return keySource;
+		return jwkSource;
 	}
 
 	private static <B extends HttpSecurityBuilder<B>> ProviderSettings getProviderSettings(B builder) {
@@ -317,6 +316,19 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
 		return builder.getSharedObject(ApplicationContext.class).getBean(type);
 	}
 
+	@SuppressWarnings("unchecked")
+	private static <B extends HttpSecurityBuilder<B>, T> T getBean(B builder, ResolvableType type) {
+		ApplicationContext context = builder.getSharedObject(ApplicationContext.class);
+		String[] names = context.getBeanNamesForType(type);
+		if (names.length == 1) {
+			return (T) context.getBean(names[0]);
+		}
+		if (names.length > 1) {
+			throw new NoUniqueBeanDefinitionException(type, names);
+		}
+		throw new NoSuchBeanDefinitionException(type);
+	}
+
 	private static <B extends HttpSecurityBuilder<B>, T> T getOptionalBean(B builder, Class<T> type) {
 		Map<String, T> beansMap = BeanFactoryUtils.beansOfTypeIncludingAncestors(
 				builder.getSharedObject(ApplicationContext.class), type);

+ 0 - 78
oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/AsymmetricKey.java

@@ -1,78 +0,0 @@
-/*
- * Copyright 2020 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.crypto.key;
-
-import org.springframework.util.Assert;
-import org.springframework.util.StringUtils;
-
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.util.Map;
-import java.util.UUID;
-
-/**
- * A {@link CryptoKey} that holds a {@code java.security.PrivateKey}
- * and {@code java.security.PublicKey} used for asymmetric algorithm's.
- *
- * @author Joe Grandja
- * @since 0.1.0
- * @see CryptoKey
- * @see PrivateKey
- * @see PublicKey
- */
-public final class AsymmetricKey extends CryptoKey<PrivateKey> {
-	private final PublicKey publicKey;
-
-	private AsymmetricKey(PrivateKey privateKey, PublicKey publicKey, String id, Map<String, Object> metadata) {
-		super(privateKey, id, metadata);
-		this.publicKey = publicKey;
-	}
-
-	/**
-	 * Returns the {@code java.security.PublicKey}.
-	 *
-	 * @return the {@code java.security.PublicKey}
-	 */
-	public PublicKey getPublicKey() {
-		return this.publicKey;
-	}
-
-	/**
-	 * A builder for {@link AsymmetricKey}.
-	 */
-	public static class Builder extends AbstractBuilder<AsymmetricKey, Builder> {
-		private PublicKey publicKey;
-
-		Builder(PrivateKey privateKey, PublicKey publicKey) {
-			super(privateKey);
-			Assert.notNull(publicKey, "publicKey cannot be null");
-			this.publicKey = publicKey;
-		}
-
-		/**
-		 * Creates the {@link AsymmetricKey}.
-		 *
-		 * @return the {@link AsymmetricKey}
-		 */
-		@Override
-		public AsymmetricKey build() {
-			if (!StringUtils.hasText(this.id)) {
-				this.id = UUID.randomUUID().toString();
-			}
-			return new AsymmetricKey((PrivateKey) this.key, this.publicKey, this.id, this.metadata);
-		}
-	}
-}

+ 0 - 231
oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/CryptoKey.java

@@ -1,231 +0,0 @@
-/*
- * Copyright 2020 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.crypto.key;
-
-import org.springframework.security.oauth2.server.authorization.Version;
-import org.springframework.util.Assert;
-
-import javax.crypto.SecretKey;
-import java.io.Serializable;
-import java.security.Key;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.Objects;
-import java.util.function.Consumer;
-
-/**
- * A holder of a {@code java.security.Key} used for cryptographic operations.
- *
- * @param <K> the type of {@code java.security.Key}
- * @author Joe Grandja
- * @since 0.1.0
- * @see CryptoKeySource
- */
-public class CryptoKey<K extends Key> implements Serializable {
-	private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
-	private final K key;
-	private final String id;
-	private final Map<String, Object> metadata;
-
-	/**
-	 * Constructs a {@code CryptoKey} using the provided parameters.
-	 *
-	 * @param key the {@code java.security.Key}
-	 * @param id the logical identifier for the {@code key}
-	 */
-	protected CryptoKey(K key, String id) {
-		this(key, id, Collections.emptyMap());
-	}
-
-	/**
-	 * Constructs a {@code CryptoKey} using the provided parameters.
-	 *
-	 * @param key the {@code java.security.Key}
-	 * @param id the logical identifier for the {@code key}
-	 * @param metadata the metadata describing the {@code key}
-	 */
-	protected CryptoKey(K key, String id, Map<String, Object> metadata) {
-		Assert.notNull(key, "key cannot be null");
-		Assert.hasText(id, "id cannot be empty");
-		Assert.notNull(metadata, "metadata cannot be null");
-		this.key = key;
-		this.id = id;
-		this.metadata = Collections.unmodifiableMap(new LinkedHashMap<>(metadata));
-	}
-
-	/**
-	 * Returns a type of {@code java.security.Key},
-	 * e.g. {@code javax.crypto.SecretKey} or {@code java.security.PrivateKey}.
-	 *
-	 * @return the type of {@code java.security.Key}
-	 */
-	public final K getKey() {
-		return this.key;
-	}
-
-	/**
-	 * Returns the logical identifier for this key.
-	 *
-	 * @return the logical identifier for this key
-	 */
-	public final String getId() {
-		return this.id;
-	}
-
-	/**
-	 * Returns the metadata value associated to this key.
-	 *
-	 * @param name the name of the metadata
-	 * @param <T> the type of the metadata
-	 * @return the metadata value, or {@code null} if not available
-	 */
-	@SuppressWarnings("unchecked")
-	public final <T> T getMetadata(String name) {
-		Assert.hasText(name, "name cannot be empty");
-		return (T) this.metadata.get(name);
-	}
-
-	/**
-	 * Returns the metadata associated to this key.
-	 *
-	 * @return a {@code Map} of the metadata
-	 */
-	public final Map<String, Object> getMetadata() {
-		return this.metadata;
-	}
-
-	/**
-	 * Returns the algorithm for this key.
-	 *
-	 * @return the algorithm for this key
-	 */
-	public final String getAlgorithm() {
-		return getKey().getAlgorithm();
-	}
-
-	@Override
-	public boolean equals(Object obj) {
-		if (this == obj) {
-			return true;
-		}
-		if (obj == null || getClass() != obj.getClass()) {
-			return false;
-		}
-		CryptoKey<?> that = (CryptoKey<?>) obj;
-		return Objects.equals(this.id, that.id);
-	}
-
-	@Override
-	public int hashCode() {
-		return Objects.hash(this.id);
-	}
-
-	/**
-	 * Create a {@link SymmetricKey} via {@link SymmetricKey.Builder}.
-	 *
-	 * @param secretKey the {@code javax.crypto.SecretKey}
-	 * @return the {@link SymmetricKey.Builder}
-	 */
-	public static SymmetricKey.Builder symmetric(SecretKey secretKey) {
-		return new SymmetricKey.Builder(secretKey);
-	}
-
-	/**
-	 * Create a {@link AsymmetricKey} via {@link AsymmetricKey.Builder}.
-	 *
-	 * @param privateKey the {@code java.security.PrivateKey}
-	 * @param publicKey the {@code java.security.PublicKey}
-	 * @return the {@link AsymmetricKey.Builder}
-	 */
-	public static AsymmetricKey.Builder asymmetric(PrivateKey privateKey, PublicKey publicKey) {
-		return new AsymmetricKey.Builder(privateKey, publicKey);
-	}
-
-	/**
-	 * Base builder for {@link CryptoKey}.
-	 *
-	 * @param <T> the type of {@link CryptoKey}
-	 * @param <B> the type of {@link AbstractBuilder}
-	 */
-	protected abstract static class AbstractBuilder<T extends CryptoKey<?>, B extends AbstractBuilder<T, B>> implements Serializable {
-		private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
-		protected Key key;
-		protected String id;
-		protected Map<String, Object> metadata = new HashMap<>();
-
-		/**
-		 * Sub-class constructor.
-		 *
-		 * @param key the {@code java.security.Key}
-		 */
-		protected AbstractBuilder(Key key) {
-			Assert.notNull(key, "key cannot be null");
-			this.key = key;
-		}
-
-		/**
-		 * Sets the logical identifier for this key.
-		 *
-		 * @param id the logical identifier for this key
-		 * @return the type of {@link AbstractBuilder}
-		 */
-		@SuppressWarnings("unchecked")
-		public B id(String id) {
-			this.id = id;
-			return (B) this;
-		}
-
-		/**
-		 * Adds metadata for this key.
-		 *
-		 * @param name the name of the metadata
-		 * @param value the value of the metadata
-		 * @return the type of {@link AbstractBuilder}
-		 */
-		@SuppressWarnings("unchecked")
-		public B metadata(String name, Object value) {
-			Assert.hasText(name, "name cannot be empty");
-			Assert.notNull(value, "value cannot be null");
-			this.metadata.put(name, value);
-			return (B) this;
-		}
-
-		/**
-		 * A {@code Consumer} of the metadata {@code Map}
-		 * allowing the ability to add, replace, or remove.
-		 *
-		 * @param metadataConsumer a {@link Consumer} of the metadata {@code Map}
-		 * @return the type of {@link AbstractBuilder}
-		 */
-		@SuppressWarnings("unchecked")
-		public B metadata(Consumer<Map<String, Object>> metadataConsumer) {
-			metadataConsumer.accept(this.metadata);
-			return (B) this;
-		}
-
-		/**
-		 * Creates the {@link CryptoKey}.
-		 *
-		 * @return the {@link CryptoKey}
-		 */
-		protected abstract T build();
-
-	}
-}

+ 0 - 38
oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/CryptoKeySource.java

@@ -1,38 +0,0 @@
-/*
- * Copyright 2020 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.crypto.key;
-
-import java.util.Set;
-
-/**
- * A source for {@link CryptoKey}'s.
- *
- * @author Joe Grandja
- * @since 0.1.0
- * @see CryptoKey
- * @see SymmetricKey
- * @see AsymmetricKey
- */
-public interface CryptoKeySource {
-
-	/**
-	 * Returns a {@code Set} of {@link CryptoKey}'s.
-	 *
-	 * @return a {@code Set} of {@link CryptoKey}'s
-	 */
-	Set<CryptoKey<?>> getKeys();
-
-}

+ 0 - 62
oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/StaticKeyGeneratingCryptoKeySource.java

@@ -1,62 +0,0 @@
-/*
- * Copyright 2020 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.crypto.key;
-
-import javax.crypto.SecretKey;
-import java.security.KeyPair;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import static org.springframework.security.crypto.key.KeyGeneratorUtils.generateRsaKey;
-import static org.springframework.security.crypto.key.KeyGeneratorUtils.generateSecretKey;
-
-/**
- * An implementation of a {@link CryptoKeySource} that generates the {@link CryptoKey}'s when constructed.
- *
- * <p>
- * <b>NOTE:</b> This implementation should ONLY be used during development/testing.
- *
- * @author Joe Grandja
- * @since 0.1.0
- * @see CryptoKeySource
- */
-public final class StaticKeyGeneratingCryptoKeySource implements CryptoKeySource {
-	private final Map<String, CryptoKey<?>> keys;
-
-	public StaticKeyGeneratingCryptoKeySource() {
-		this.keys = Collections.unmodifiableMap(generateKeys());
-	}
-
-	@Override
-	public Set<CryptoKey<?>> getKeys() {
-		return new HashSet<>(this.keys.values());
-	}
-
-	private static Map<String, CryptoKey<?>> generateKeys() {
-		KeyPair rsaKeyPair = generateRsaKey();
-		AsymmetricKey asymmetricKey = CryptoKey.asymmetric(rsaKeyPair.getPrivate(), rsaKeyPair.getPublic()).build();
-
-		SecretKey hmacKey = generateSecretKey();
-		SymmetricKey symmetricKey = CryptoKey.symmetric(hmacKey).build();
-
-		return Stream.of(asymmetricKey, symmetricKey)
-				.collect(Collectors.toMap(CryptoKey::getId, v -> v));
-	}
-}

+ 0 - 61
oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/SymmetricKey.java

@@ -1,61 +0,0 @@
-/*
- * Copyright 2020 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.crypto.key;
-
-import org.springframework.util.StringUtils;
-
-import javax.crypto.SecretKey;
-import java.util.Map;
-import java.util.UUID;
-
-/**
- * A {@link CryptoKey} that holds a {@code javax.crypto.SecretKey}
- * used for symmetric algorithm's.
- *
- * @author Joe Grandja
- * @since 0.1.0
- * @see CryptoKey
- * @see SecretKey
- */
-public final class SymmetricKey extends CryptoKey<SecretKey> {
-
-	private SymmetricKey(SecretKey key, String id, Map<String, Object> metadata) {
-		super(key, id, metadata);
-	}
-
-	/**
-	 * A builder for {@link SymmetricKey}.
-	 */
-	public static class Builder extends AbstractBuilder<SymmetricKey, Builder> {
-
-		Builder(SecretKey secretKey) {
-			super(secretKey);
-		}
-
-		/**
-		 * Creates the {@link SymmetricKey}.
-		 *
-		 * @return the {@link SymmetricKey}
-		 */
-		@Override
-		public SymmetricKey build() {
-			if (!StringUtils.hasText(this.id)) {
-				this.id = UUID.randomUUID().toString();
-			}
-			return new SymmetricKey((SecretKey) this.key, this.id, this.metadata);
-		}
-	}
-}

+ 47 - 46
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/jose/JoseHeader.java → oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/jwt/JoseHeader.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 the original author or authors.
+ * Copyright 2020-2021 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.
@@ -13,30 +13,19 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.springframework.security.oauth2.jose;
-
-import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
-import org.springframework.security.oauth2.jwt.Jwt;
-import org.springframework.util.Assert;
+package org.springframework.security.oauth2.jwt;
 
+import java.net.URL;
 import java.util.Collections;
-import java.util.LinkedHashMap;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.function.Consumer;
 
-import static org.springframework.security.oauth2.jose.JoseHeaderNames.ALG;
-import static org.springframework.security.oauth2.jose.JoseHeaderNames.CRIT;
-import static org.springframework.security.oauth2.jose.JoseHeaderNames.CTY;
-import static org.springframework.security.oauth2.jose.JoseHeaderNames.JKU;
-import static org.springframework.security.oauth2.jose.JoseHeaderNames.JWK;
-import static org.springframework.security.oauth2.jose.JoseHeaderNames.KID;
-import static org.springframework.security.oauth2.jose.JoseHeaderNames.TYP;
-import static org.springframework.security.oauth2.jose.JoseHeaderNames.X5C;
-import static org.springframework.security.oauth2.jose.JoseHeaderNames.X5T;
-import static org.springframework.security.oauth2.jose.JoseHeaderNames.X5T_S256;
-import static org.springframework.security.oauth2.jose.JoseHeaderNames.X5U;
+import org.springframework.security.oauth2.core.converter.ClaimConversionService;
+import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
+import org.springframework.util.Assert;
 
 /**
  * The JOSE header is a JSON object representing the header parameters of a JSON Web Token,
@@ -55,16 +44,16 @@ public final class JoseHeader {
 	private final Map<String, Object> headers;
 
 	private JoseHeader(Map<String, Object> headers) {
-		this.headers = Collections.unmodifiableMap(new LinkedHashMap<>(headers));
+		this.headers = Collections.unmodifiableMap(new HashMap<>(headers));
 	}
 
 	/**
-	 * Returns the JWS algorithm used to digitally sign the JWS.
+	 * Returns the {@link JwsAlgorithm JWS algorithm} used to digitally sign the JWS.
 	 *
 	 * @return the JWS algorithm
 	 */
 	public JwsAlgorithm getJwsAlgorithm() {
-		return getHeader(ALG);
+		return getHeader(JoseHeaderNames.ALG);
 	}
 
 	/**
@@ -73,8 +62,8 @@ public final class JoseHeader {
 	 *
 	 * @return the JWK Set URL
 	 */
-	public String getJwkSetUri() {
-		return getHeader(JKU);
+	public URL getJwkSetUri() {
+		return getHeader(JoseHeaderNames.JKU);
 	}
 
 	/**
@@ -84,7 +73,7 @@ public final class JoseHeader {
 	 * @return the JSON Web Key
 	 */
 	public Map<String, Object> getJwk() {
-		return getHeader(JWK);
+		return getHeader(JoseHeaderNames.JWK);
 	}
 
 	/**
@@ -93,7 +82,7 @@ public final class JoseHeader {
 	 * @return the key ID
 	 */
 	public String getKeyId() {
-		return getHeader(KID);
+		return getHeader(JoseHeaderNames.KID);
 	}
 
 	/**
@@ -102,8 +91,8 @@ public final class JoseHeader {
 	 *
 	 * @return the X.509 URL
 	 */
-	public String getX509Uri() {
-		return getHeader(X5U);
+	public URL getX509Uri() {
+		return getHeader(JoseHeaderNames.X5U);
 	}
 
 	/**
@@ -113,7 +102,7 @@ public final class JoseHeader {
 	 * @return the X.509 certificate chain
 	 */
 	public List<String> getX509CertificateChain() {
-		return getHeader(X5C);
+		return getHeader(JoseHeaderNames.X5C);
 	}
 
 	/**
@@ -123,7 +112,7 @@ public final class JoseHeader {
 	 * @return the X.509 certificate SHA-1 thumbprint
 	 */
 	public String getX509SHA1Thumbprint() {
-		return getHeader(X5T);
+		return getHeader(JoseHeaderNames.X5T);
 	}
 
 	/**
@@ -133,7 +122,7 @@ public final class JoseHeader {
 	 * @return the X.509 certificate SHA-256 thumbprint
 	 */
 	public String getX509SHA256Thumbprint() {
-		return getHeader(X5T_S256);
+		return getHeader(JoseHeaderNames.X5T_S256);
 	}
 
 	/**
@@ -143,7 +132,7 @@ public final class JoseHeader {
 	 * @return the critical headers
 	 */
 	public Set<String> getCritical() {
-		return getHeader(CRIT);
+		return getHeader(JoseHeaderNames.CRIT);
 	}
 
 	/**
@@ -152,7 +141,7 @@ public final class JoseHeader {
 	 * @return the type header
 	 */
 	public String getType() {
-		return getHeader(TYP);
+		return getHeader(JoseHeaderNames.TYP);
 	}
 
 	/**
@@ -161,7 +150,7 @@ public final class JoseHeader {
 	 * @return the content type header
 	 */
 	public String getContentType() {
-		return getHeader(CTY);
+		return getHeader(JoseHeaderNames.CTY);
 	}
 
 	/**
@@ -209,12 +198,12 @@ public final class JoseHeader {
 	/**
 	 * A builder for {@link JoseHeader}.
 	 */
-	public static class Builder {
-		private final Map<String, Object> headers = new LinkedHashMap<>();
+	public static final class Builder {
+		private final Map<String, Object> headers = new HashMap<>();
 
 		private Builder(JwsAlgorithm jwsAlgorithm) {
 			Assert.notNull(jwsAlgorithm, "jwsAlgorithm cannot be null");
-			header(ALG, jwsAlgorithm);
+			header(JoseHeaderNames.ALG, jwsAlgorithm);
 		}
 
 		private Builder(JoseHeader headers) {
@@ -230,7 +219,7 @@ public final class JoseHeader {
 		 * @return the {@link Builder}
 		 */
 		public Builder jwkSetUri(String jwkSetUri) {
-			return header(JKU, jwkSetUri);
+			return header(JoseHeaderNames.JKU, jwkSetUri);
 		}
 
 		/**
@@ -241,7 +230,7 @@ public final class JoseHeader {
 		 * @return the {@link Builder}
 		 */
 		public Builder jwk(Map<String, Object> jwk) {
-			return header(JWK, jwk);
+			return header(JoseHeaderNames.JWK, jwk);
 		}
 
 		/**
@@ -251,7 +240,7 @@ public final class JoseHeader {
 		 * @return the {@link Builder}
 		 */
 		public Builder keyId(String keyId) {
-			return header(KID, keyId);
+			return header(JoseHeaderNames.KID, keyId);
 		}
 
 		/**
@@ -262,7 +251,7 @@ public final class JoseHeader {
 		 * @return the {@link Builder}
 		 */
 		public Builder x509Uri(String x509Uri) {
-			return header(X5U, x509Uri);
+			return header(JoseHeaderNames.X5U, x509Uri);
 		}
 
 		/**
@@ -273,7 +262,7 @@ public final class JoseHeader {
 		 * @return the {@link Builder}
 		 */
 		public Builder x509CertificateChain(List<String> x509CertificateChain) {
-			return header(X5C, x509CertificateChain);
+			return header(JoseHeaderNames.X5C, x509CertificateChain);
 		}
 
 		/**
@@ -284,7 +273,7 @@ public final class JoseHeader {
 		 * @return the {@link Builder}
 		 */
 		public Builder x509SHA1Thumbprint(String x509SHA1Thumbprint) {
-			return header(X5T, x509SHA1Thumbprint);
+			return header(JoseHeaderNames.X5T, x509SHA1Thumbprint);
 		}
 
 		/**
@@ -295,7 +284,7 @@ public final class JoseHeader {
 		 * @return the {@link Builder}
 		 */
 		public Builder x509SHA256Thumbprint(String x509SHA256Thumbprint) {
-			return header(X5T_S256, x509SHA256Thumbprint);
+			return header(JoseHeaderNames.X5T_S256, x509SHA256Thumbprint);
 		}
 
 		/**
@@ -306,7 +295,7 @@ public final class JoseHeader {
 		 * @return the {@link Builder}
 		 */
 		public Builder critical(Set<String> headerNames) {
-			return header(CRIT, headerNames);
+			return header(JoseHeaderNames.CRIT, headerNames);
 		}
 
 		/**
@@ -316,7 +305,7 @@ public final class JoseHeader {
 		 * @return the {@link Builder}
 		 */
 		public Builder type(String type) {
-			return header(TYP, type);
+			return header(JoseHeaderNames.TYP, type);
 		}
 
 		/**
@@ -326,7 +315,7 @@ public final class JoseHeader {
 		 * @return the {@link Builder}
 		 */
 		public Builder contentType(String contentType) {
-			return header(CTY, contentType);
+			return header(JoseHeaderNames.CTY, contentType);
 		}
 
 		/**
@@ -362,7 +351,19 @@ public final class JoseHeader {
 		 */
 		public JoseHeader build() {
 			Assert.notEmpty(this.headers, "headers cannot be empty");
+			convertAsURL(JoseHeaderNames.JKU);
+			convertAsURL(JoseHeaderNames.X5U);
 			return new JoseHeader(this.headers);
 		}
+
+		private void convertAsURL(String header) {
+			Object value = this.headers.get(header);
+			if (value != null) {
+				URL convertedValue = ClaimConversionService.getSharedInstance().convert(value, URL.class);
+				Assert.isTrue(convertedValue != null,
+						() -> "Unable to convert header '" + header + "' of type '" + value.getClass() + "' to URL.");
+				this.headers.put(header, convertedValue);
+			}
+		}
 	}
 }

+ 17 - 14
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/jose/JoseHeaderNames.java → oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/jwt/JoseHeaderNames.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 the original author or authors.
+ * Copyright 2020-2021 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.
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.springframework.security.oauth2.jose;
+package org.springframework.security.oauth2.jwt;
 
 /**
  * The Registered Header Parameter Names defined by the JSON Web Token (JWT),
@@ -28,69 +28,72 @@ package org.springframework.security.oauth2.jose;
  * @see <a target="_blank" href="https://tools.ietf.org/html/rfc7515#section-4">JWS JOSE Header</a>
  * @see <a target="_blank" href="https://tools.ietf.org/html/rfc7516#section-4">JWE JOSE Header</a>
  */
-public interface JoseHeaderNames {
+public final class JoseHeaderNames {
 
 	/**
 	 * {@code alg} - the algorithm header identifies the cryptographic algorithm used to secure a JWS or JWE
 	 */
-	String ALG = "alg";
+	public static final String ALG = "alg";
 
 	/**
 	 * {@code jku} - the JWK Set URL header is a URI that refers to a resource for a set of JSON-encoded public keys,
 	 * one of which corresponds to the key used to digitally sign a JWS or encrypt a JWE
 	 */
-	String JKU = "jku";
+	public static final String JKU = "jku";
 
 	/**
 	 * {@code jwk} - the JSON Web Key header is the public key that corresponds to the key
 	 * used to digitally sign a JWS or encrypt a JWE
 	 */
-	String JWK = "jwk";
+	public static final String JWK = "jwk";
 
 	/**
 	 * {@code kid} - the key ID header is a hint indicating which key was used to secure a JWS or JWE
 	 */
-	String KID = "kid";
+	public static final String KID = "kid";
 
 	/**
 	 * {@code x5u} - the X.509 URL header is a URI that refers to a resource for the X.509 public key certificate
 	 * or certificate chain corresponding to the key used to digitally sign a JWS or encrypt a JWE
 	 */
-	String X5U = "x5u";
+	public static final String X5U = "x5u";
 
 	/**
 	 * {@code x5c} - the X.509 certificate chain header contains the X.509 public key certificate
 	 * or certificate chain corresponding to the key used to digitally sign a JWS or encrypt a JWE
 	 */
-	String X5C = "x5c";
+	public static final String X5C = "x5c";
 
 	/**
 	 * {@code x5t} - the X.509 certificate SHA-1 thumbprint header is a base64url-encoded SHA-1 thumbprint (a.k.a. digest)
 	 * of the DER encoding of the X.509 certificate corresponding to the key used to digitally sign a JWS or encrypt a JWE
 	 */
-	String X5T = "x5t";
+	public static final String X5T = "x5t";
 
 	/**
 	 * {@code x5t#S256} - the X.509 certificate SHA-256 thumbprint header is a base64url-encoded SHA-256 thumbprint (a.k.a. digest)
 	 * of the DER encoding of the X.509 certificate corresponding to the key used to digitally sign a JWS or encrypt a JWE
 	 */
-	String X5T_S256 = "x5t#S256";
+	public static final String X5T_S256 = "x5t#S256";
 
 	/**
 	 * {@code typ} - the type header is used by JWS/JWE applications to declare the media type of a JWS/JWE
 	 */
-	String TYP = "typ";
+	public static final String TYP = "typ";
 
 	/**
 	 * {@code cty} - the content type header is used by JWS/JWE applications to declare the media type
 	 * of the secured content (the payload)
 	 */
-	String CTY = "cty";
+	public static final String CTY = "cty";
 
 	/**
 	 * {@code crit} - the critical header indicates that extensions to the JWS/JWE/JWA specifications
 	 * are being used that MUST be understood and processed
 	 */
-	String CRIT = "crit";
+	public static final String CRIT = "crit";
+
+	private JoseHeaderNames() {
+	}
 
 }

+ 13 - 21
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/jwt/JwtClaimsSet.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 the original author or authors.
+ * Copyright 2020-2021 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.
@@ -15,22 +15,14 @@
  */
 package org.springframework.security.oauth2.jwt;
 
-import org.springframework.util.Assert;
-
 import java.time.Instant;
 import java.util.Collections;
-import java.util.LinkedHashMap;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Consumer;
 
-import static org.springframework.security.oauth2.jwt.JwtClaimNames.AUD;
-import static org.springframework.security.oauth2.jwt.JwtClaimNames.EXP;
-import static org.springframework.security.oauth2.jwt.JwtClaimNames.IAT;
-import static org.springframework.security.oauth2.jwt.JwtClaimNames.ISS;
-import static org.springframework.security.oauth2.jwt.JwtClaimNames.JTI;
-import static org.springframework.security.oauth2.jwt.JwtClaimNames.NBF;
-import static org.springframework.security.oauth2.jwt.JwtClaimNames.SUB;
+import org.springframework.util.Assert;
 
 /**
  * The {@link Jwt JWT} Claims Set is a JSON object representing the claims conveyed by a JSON Web Token.
@@ -46,7 +38,7 @@ public final class JwtClaimsSet implements JwtClaimAccessor {
 	private final Map<String, Object> claims;
 
 	private JwtClaimsSet(Map<String, Object> claims) {
-		this.claims = Collections.unmodifiableMap(new LinkedHashMap<>(claims));
+		this.claims = Collections.unmodifiableMap(new HashMap<>(claims));
 	}
 
 	@Override
@@ -76,8 +68,8 @@ public final class JwtClaimsSet implements JwtClaimAccessor {
 	/**
 	 * A builder for {@link JwtClaimsSet}.
 	 */
-	public static class Builder {
-		private final Map<String, Object> claims = new LinkedHashMap<>();
+	public static final class Builder {
+		private final Map<String, Object> claims = new HashMap<>();
 
 		private Builder() {
 		}
@@ -94,7 +86,7 @@ public final class JwtClaimsSet implements JwtClaimAccessor {
 		 * @return the {@link Builder}
 		 */
 		public Builder issuer(String issuer) {
-			return claim(ISS, issuer);
+			return claim(JwtClaimNames.ISS, issuer);
 		}
 
 		/**
@@ -104,7 +96,7 @@ public final class JwtClaimsSet implements JwtClaimAccessor {
 		 * @return the {@link Builder}
 		 */
 		public Builder subject(String subject) {
-			return claim(SUB, subject);
+			return claim(JwtClaimNames.SUB, subject);
 		}
 
 		/**
@@ -114,7 +106,7 @@ public final class JwtClaimsSet implements JwtClaimAccessor {
 		 * @return the {@link Builder}
 		 */
 		public Builder audience(List<String> audience) {
-			return claim(AUD, audience);
+			return claim(JwtClaimNames.AUD, audience);
 		}
 
 		/**
@@ -125,7 +117,7 @@ public final class JwtClaimsSet implements JwtClaimAccessor {
 		 * @return the {@link Builder}
 		 */
 		public Builder expiresAt(Instant expiresAt) {
-			return claim(EXP, expiresAt);
+			return claim(JwtClaimNames.EXP, expiresAt);
 		}
 
 		/**
@@ -136,7 +128,7 @@ public final class JwtClaimsSet implements JwtClaimAccessor {
 		 * @return the {@link Builder}
 		 */
 		public Builder notBefore(Instant notBefore) {
-			return claim(NBF, notBefore);
+			return claim(JwtClaimNames.NBF, notBefore);
 		}
 
 		/**
@@ -146,7 +138,7 @@ public final class JwtClaimsSet implements JwtClaimAccessor {
 		 * @return the {@link Builder}
 		 */
 		public Builder issuedAt(Instant issuedAt) {
-			return claim(IAT, issuedAt);
+			return claim(JwtClaimNames.IAT, issuedAt);
 		}
 
 		/**
@@ -156,7 +148,7 @@ public final class JwtClaimsSet implements JwtClaimAccessor {
 		 * @return the {@link Builder}
 		 */
 		public Builder id(String jti) {
-			return claim(JTI, jti);
+			return claim(JwtClaimNames.JTI, jti);
 		}
 
 		/**

+ 1 - 3
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/jwt/JwtEncoder.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 the original author or authors.
+ * Copyright 2020-2021 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.
@@ -15,8 +15,6 @@
  */
 package org.springframework.security.oauth2.jwt;
 
-import org.springframework.security.oauth2.jose.JoseHeader;
-
 /**
  * Implementations of this interface are responsible for encoding
  * a JSON Web Token (JWT) to it's compact claims representation format.

+ 137 - 139
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/jose/jws/NimbusJwsEncoder.java → oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwsEncoder.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 the original author or authors.
+ * Copyright 2020-2021 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.
@@ -13,54 +13,49 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.springframework.security.oauth2.jose.jws;
+package org.springframework.security.oauth2.jwt;
+
+import java.net.URL;
+import java.time.Instant;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiConsumer;
+import java.util.stream.Collectors;
 
 import com.nimbusds.jose.JOSEException;
 import com.nimbusds.jose.JOSEObjectType;
 import com.nimbusds.jose.JWSAlgorithm;
 import com.nimbusds.jose.JWSHeader;
 import com.nimbusds.jose.JWSSigner;
-import com.nimbusds.jose.KeyLengthException;
-import com.nimbusds.jose.crypto.MACSigner;
-import com.nimbusds.jose.crypto.RSASSASigner;
+import com.nimbusds.jose.KeySourceException;
+import com.nimbusds.jose.crypto.factories.DefaultJWSSignerFactory;
 import com.nimbusds.jose.jwk.JWK;
+import com.nimbusds.jose.jwk.JWKMatcher;
+import com.nimbusds.jose.jwk.JWKSelector;
+import com.nimbusds.jose.jwk.source.JWKSource;
+import com.nimbusds.jose.proc.SecurityContext;
+import com.nimbusds.jose.produce.JWSSignerFactory;
 import com.nimbusds.jose.util.Base64;
 import com.nimbusds.jose.util.Base64URL;
 import com.nimbusds.jwt.JWTClaimsSet;
 import com.nimbusds.jwt.SignedJWT;
+
 import org.springframework.core.convert.converter.Converter;
-import org.springframework.security.crypto.key.AsymmetricKey;
-import org.springframework.security.crypto.key.CryptoKey;
-import org.springframework.security.crypto.key.CryptoKeySource;
-import org.springframework.security.oauth2.jose.JoseHeader;
-import org.springframework.security.oauth2.jose.JoseHeaderNames;
-import org.springframework.security.oauth2.jwt.Jwt;
-import org.springframework.security.oauth2.jwt.JwtClaimsSet;
-import org.springframework.security.oauth2.jwt.JwtEncoder;
-import org.springframework.security.oauth2.jwt.JwtEncodingException;
+import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
 import org.springframework.util.Assert;
 import org.springframework.util.CollectionUtils;
 import org.springframework.util.StringUtils;
 
-import javax.crypto.SecretKey;
-import java.net.URI;
-import java.net.URL;
-import java.security.PrivateKey;
-import java.time.Instant;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.UUID;
-import java.util.function.BiConsumer;
-import java.util.stream.Collectors;
-
 /**
- * An implementation of a {@link JwtEncoder} that encodes a JSON Web Token (JWT)
- * using the JSON Web Signature (JWS) Compact Serialization format.
- * The private/secret key used for signing the JWS is obtained
- * from the {@link CryptoKeySource} supplied via the constructor.
+ * An implementation of a {@link JwtEncoder} that encodes a JSON Web Token (JWT) using the
+ * JSON Web Signature (JWS) Compact Serialization format. The private/secret key used for
+ * signing the JWS is supplied by the {@code com.nimbusds.jose.jwk.source.JWKSource}
+ * provided via the constructor.
  *
  * <p>
  * <b>NOTE:</b> This implementation uses the Nimbus JOSE + JWT SDK.
@@ -68,50 +63,46 @@ import java.util.stream.Collectors;
  * @author Joe Grandja
  * @since 0.0.1
  * @see JwtEncoder
- * @see CryptoKeySource
- * @see <a target="_blank" href="https://tools.ietf.org/html/rfc7519">JSON Web Token (JWT)</a>
- * @see <a target="_blank" href="https://tools.ietf.org/html/rfc7515">JSON Web Signature (JWS)</a>
- * @see <a target="_blank" href="https://tools.ietf.org/html/rfc7515#section-3.1">JWS Compact Serialization</a>
- * @see <a target="_blank" href="https://connect2id.com/products/nimbus-jose-jwt">Nimbus JOSE + JWT SDK</a>
+ * @see com.nimbusds.jose.jwk.source.JWKSource
+ * @see com.nimbusds.jose.jwk.JWK
+ * @see <a target="_blank" href="https://tools.ietf.org/html/rfc7519">JSON Web Token
+ * (JWT)</a>
+ * @see <a target="_blank" href="https://tools.ietf.org/html/rfc7515">JSON Web Signature
+ * (JWS)</a>
+ * @see <a target="_blank" href="https://tools.ietf.org/html/rfc7515#section-3.1">JWS
+ * Compact Serialization</a>
+ * @see <a target="_blank" href="https://connect2id.com/products/nimbus-jose-jwt">Nimbus
+ * JOSE + JWT SDK</a>
  */
 public final class NimbusJwsEncoder implements JwtEncoder {
-	private static final String ENCODING_ERROR_MESSAGE_TEMPLATE =
-			"An error occurred while attempting to encode the Jwt: %s";
-	private static final String RSA_KEY_TYPE = "RSA";
-	private static final String EC_KEY_TYPE = "EC";
-	private static final Map<JwsAlgorithm, String> jcaKeyAlgorithmMappings = new HashMap<JwsAlgorithm, String>() {
-		{
-			put(MacAlgorithm.HS256, "HmacSHA256");
-			put(MacAlgorithm.HS384, "HmacSHA384");
-			put(MacAlgorithm.HS512, "HmacSHA512");
-			put(SignatureAlgorithm.RS256, RSA_KEY_TYPE);
-			put(SignatureAlgorithm.RS384, RSA_KEY_TYPE);
-			put(SignatureAlgorithm.RS512, RSA_KEY_TYPE);
-			put(SignatureAlgorithm.ES256, EC_KEY_TYPE);
-			put(SignatureAlgorithm.ES384, EC_KEY_TYPE);
-			put(SignatureAlgorithm.ES512, EC_KEY_TYPE);
-		}
+
+	private static final String ENCODING_ERROR_MESSAGE_TEMPLATE = "An error occurred while attempting to encode the Jwt: %s";
+
+	private static final Converter<JoseHeader, JWSHeader> JWS_HEADER_CONVERTER = new JwsHeaderConverter();
+
+	private static final Converter<JwtClaimsSet, JWTClaimsSet> JWT_CLAIMS_SET_CONVERTER = new JwtClaimsSetConverter();
+
+	private static final JWSSignerFactory JWS_SIGNER_FACTORY = new DefaultJWSSignerFactory();
+
+	private final Map<JWK, JWSSigner> jwsSigners = new ConcurrentHashMap<>();
+
+	private final JWKSource<SecurityContext> jwkSource;
+
+	private BiConsumer<JoseHeader.Builder, JwtClaimsSet.Builder> jwtCustomizer = (headers, claims) -> {
 	};
-	private static final Converter<JoseHeader, JWSHeader> jwsHeaderConverter = new JwsHeaderConverter();
-	private static final Converter<JwtClaimsSet, JWTClaimsSet> jwtClaimsSetConverter = new JwtClaimsSetConverter();
-	private final CryptoKeySource keySource;
-	private BiConsumer<JoseHeader.Builder, JwtClaimsSet.Builder> jwtCustomizer = (headers, claims) -> {};
 
 	/**
 	 * Constructs a {@code NimbusJwsEncoder} using the provided parameters.
-	 *
-	 * @param keySource the source for cryptographic keys
+	 * @param jwkSource the {@code com.nimbusds.jose.jwk.source.JWKSource}
 	 */
-	public NimbusJwsEncoder(CryptoKeySource keySource) {
-		Assert.notNull(keySource, "keySource cannot be null");
-		this.keySource = keySource;
+	public NimbusJwsEncoder(JWKSource<SecurityContext> jwkSource) {
+		Assert.notNull(jwkSource, "jwkSource cannot be null");
+		this.jwkSource = jwkSource;
 	}
 
 	/**
-	 * Sets the {@link Jwt} customizer to be provided the
-	 * {@link JoseHeader.Builder} and {@link JwtClaimsSet.Builder}
-	 * allowing for further customizations.
-	 *
+	 * Sets the {@link Jwt} customizer to be provided the {@link JoseHeader.Builder} and
+	 * {@link JwtClaimsSet.Builder} allowing for further customizations.
 	 * @param jwtCustomizer the {@link Jwt} customizer to be provided the
 	 * {@link JoseHeader.Builder} and {@link JwtClaimsSet.Builder}
 	 */
@@ -125,78 +116,85 @@ public final class NimbusJwsEncoder implements JwtEncoder {
 		Assert.notNull(headers, "headers cannot be null");
 		Assert.notNull(claims, "claims cannot be null");
 
-		CryptoKey<?> cryptoKey = selectKey(headers);
-		if (cryptoKey == null) {
-			throw new JwtEncodingException(String.format(
-					ENCODING_ERROR_MESSAGE_TEMPLATE,
-					"Unsupported key for algorithm '" + headers.getJwsAlgorithm().getName() + "'"));
-		}
-
-		JWSSigner jwsSigner;
-		if (AsymmetricKey.class.isAssignableFrom(cryptoKey.getClass())) {
-			if (!cryptoKey.getAlgorithm().equals(RSA_KEY_TYPE)) {
-				throw new JwtEncodingException(String.format(
-						ENCODING_ERROR_MESSAGE_TEMPLATE,
-						"Unsupported key type '" + cryptoKey.getAlgorithm() + "'"));
-			}
-			PrivateKey privateKey = (PrivateKey) cryptoKey.getKey();
-			jwsSigner = new RSASSASigner(privateKey);
-		} else {
-			SecretKey secretKey = (SecretKey) cryptoKey.getKey();
-			try {
-				jwsSigner = new MACSigner(secretKey);
-			} catch (KeyLengthException ex) {
-				throw new JwtEncodingException(String.format(
-						ENCODING_ERROR_MESSAGE_TEMPLATE, ex.getMessage()), ex);
-			}
-		}
-
+		// @formatter:off
 		JoseHeader.Builder headersBuilder = JoseHeader.from(headers)
-				.type(JOSEObjectType.JWT.getType())
-				.keyId(cryptoKey.getId());
+				.type(JOSEObjectType.JWT.getType());
 		JwtClaimsSet.Builder claimsBuilder = JwtClaimsSet.from(claims)
 				.id(UUID.randomUUID().toString());
+		// @formatter:on
 
 		this.jwtCustomizer.accept(headersBuilder, claimsBuilder);
 
-		headers = headersBuilder.build();
+		JWK jwk = selectJwk(headersBuilder);
+		if (jwk == null) {
+			throw new JwtEncodingException(
+					String.format(ENCODING_ERROR_MESSAGE_TEMPLATE, "Failed to select a JWK signing key"));
+		}
+		else if (!StringUtils.hasText(jwk.getKeyID())) {
+			throw new JwtEncodingException(String.format(ENCODING_ERROR_MESSAGE_TEMPLATE,
+					"The \"kid\" (key ID) from the selected JWK cannot be empty"));
+		}
+
+		headers = headersBuilder.keyId(jwk.getKeyID()).build();
 		claims = claimsBuilder.build();
 
-		JWSHeader jwsHeader = jwsHeaderConverter.convert(headers);
-		JWTClaimsSet jwtClaimsSet = jwtClaimsSetConverter.convert(claims);
+		JWSHeader jwsHeader = JWS_HEADER_CONVERTER.convert(headers);
+		JWTClaimsSet jwtClaimsSet = JWT_CLAIMS_SET_CONVERTER.convert(claims);
+
+		JWSSigner jwsSigner = this.jwsSigners.computeIfAbsent(jwk, (key) -> {
+			try {
+				return JWS_SIGNER_FACTORY.createJWSSigner(key);
+			}
+			catch (JOSEException ex) {
+				throw new JwtEncodingException(String.format(ENCODING_ERROR_MESSAGE_TEMPLATE,
+						"Failed to create a JWS Signer -> " + ex.getMessage()), ex);
+			}
+		});
 
-		SignedJWT signedJWT = new SignedJWT(jwsHeader, jwtClaimsSet);
+		SignedJWT signedJwt = new SignedJWT(jwsHeader, jwtClaimsSet);
 		try {
-			signedJWT.sign(jwsSigner);
-		} catch (JOSEException ex) {
-			throw new JwtEncodingException(String.format(
-					ENCODING_ERROR_MESSAGE_TEMPLATE, ex.getMessage()), ex);
+			signedJwt.sign(jwsSigner);
 		}
-		String jws = signedJWT.serialize();
+		catch (JOSEException ex) {
+			throw new JwtEncodingException(
+					String.format(ENCODING_ERROR_MESSAGE_TEMPLATE, "Failed to sign the JWT -> " + ex.getMessage()), ex);
+		}
+		String jws = signedJwt.serialize();
 
-		return new Jwt(jws, claims.getIssuedAt(), claims.getExpiresAt(),
-				headers.getHeaders(), claims.getClaims());
+		return new Jwt(jws, claims.getIssuedAt(), claims.getExpiresAt(), headers.getHeaders(), claims.getClaims());
 	}
 
-	private CryptoKey<?> selectKey(JoseHeader headers) {
-		JwsAlgorithm jwsAlgorithm = headers.getJwsAlgorithm();
-		String keyAlgorithm = jcaKeyAlgorithmMappings.get(jwsAlgorithm);
-		if (!StringUtils.hasText(keyAlgorithm)) {
-			return null;
+	private JWK selectJwk(JoseHeader.Builder headersBuilder) {
+		final AtomicReference<JWSAlgorithm> jwsAlgorithm = new AtomicReference<>();
+		headersBuilder.headers((h) -> {
+			JwsAlgorithm jwsAlg = (JwsAlgorithm) h.get(JoseHeaderNames.ALG);
+			jwsAlgorithm.set(JWSAlgorithm.parse(jwsAlg.getName()));
+		});
+		JWSHeader jwsHeader = new JWSHeader(jwsAlgorithm.get());
+		JWKSelector jwkSelector = new JWKSelector(JWKMatcher.forJWSHeader(jwsHeader));
+
+		List<JWK> jwks;
+		try {
+			jwks = this.jwkSource.get(jwkSelector, null);
+		}
+		catch (KeySourceException ex) {
+			throw new JwtEncodingException(String.format(ENCODING_ERROR_MESSAGE_TEMPLATE,
+					"Failed to select a JWK signing key -> " + ex.getMessage()), ex);
+		}
+
+		if (jwks.size() > 1) {
+			throw new JwtEncodingException(String.format(ENCODING_ERROR_MESSAGE_TEMPLATE,
+					"Found multiple JWK signing keys for algorithm '" + jwsAlgorithm.get().getName() + "'"));
 		}
 
-		return this.keySource.getKeys().stream()
-				.filter(key -> key.getAlgorithm().equals(keyAlgorithm))
-				.findFirst()
-				.orElse(null);
+		return !jwks.isEmpty() ? jwks.get(0) : null;
 	}
 
 	private static class JwsHeaderConverter implements Converter<JoseHeader, JWSHeader> {
 
 		@Override
 		public JWSHeader convert(JoseHeader headers) {
-			JWSHeader.Builder builder = new JWSHeader.Builder(
-					JWSAlgorithm.parse(headers.getJwsAlgorithm().getName()));
+			JWSHeader.Builder builder = new JWSHeader.Builder(JWSAlgorithm.parse(headers.getJwsAlgorithm().getName()));
 
 			Set<String> critical = headers.getCritical();
 			if (!CollectionUtils.isEmpty(critical)) {
@@ -208,14 +206,14 @@ public final class NimbusJwsEncoder implements JwtEncoder {
 				builder.contentType(contentType);
 			}
 
-			String jwkSetUri = headers.getJwkSetUri();
-			if (StringUtils.hasText(jwkSetUri)) {
+			URL jwkSetUri = headers.getJwkSetUri();
+			if (jwkSetUri != null) {
 				try {
-					builder.jwkURL(new URI(jwkSetUri));
-				} catch (Exception ex) {
-					throw new JwtEncodingException(String.format(
-							ENCODING_ERROR_MESSAGE_TEMPLATE,
-							"Failed to convert '" + JoseHeaderNames.JKU + "' JOSE header"), ex);
+					builder.jwkURL(jwkSetUri.toURI());
+				}
+				catch (Exception ex) {
+					throw new JwtEncodingException(String.format(ENCODING_ERROR_MESSAGE_TEMPLATE,
+							"Failed to convert '" + JoseHeaderNames.JKU + "' JOSE header to a URI"), ex);
 				}
 			}
 
@@ -223,9 +221,9 @@ public final class NimbusJwsEncoder implements JwtEncoder {
 			if (!CollectionUtils.isEmpty(jwk)) {
 				try {
 					builder.jwk(JWK.parse(jwk));
-				} catch (Exception ex) {
-					throw new JwtEncodingException(String.format(
-							ENCODING_ERROR_MESSAGE_TEMPLATE,
+				}
+				catch (Exception ex) {
+					throw new JwtEncodingException(String.format(ENCODING_ERROR_MESSAGE_TEMPLATE,
 							"Failed to convert '" + JoseHeaderNames.JWK + "' JOSE header"), ex);
 				}
 			}
@@ -242,10 +240,7 @@ public final class NimbusJwsEncoder implements JwtEncoder {
 
 			List<String> x509CertificateChain = headers.getX509CertificateChain();
 			if (!CollectionUtils.isEmpty(x509CertificateChain)) {
-				builder.x509CertChain(
-						x509CertificateChain.stream()
-								.map(Base64::new)
-								.collect(Collectors.toList()));
+				builder.x509CertChain(x509CertificateChain.stream().map(Base64::new).collect(Collectors.toList()));
 			}
 
 			String x509SHA1Thumbprint = headers.getX509SHA1Thumbprint();
@@ -258,19 +253,19 @@ public final class NimbusJwsEncoder implements JwtEncoder {
 				builder.x509CertSHA256Thumbprint(new Base64URL(x509SHA256Thumbprint));
 			}
 
-			String x509Uri = headers.getX509Uri();
-			if (StringUtils.hasText(x509Uri)) {
+			URL x509Uri = headers.getX509Uri();
+			if (x509Uri != null) {
 				try {
-					builder.x509CertURL(new URI(x509Uri));
-				} catch (Exception ex) {
-					throw new JwtEncodingException(String.format(
-							ENCODING_ERROR_MESSAGE_TEMPLATE,
-							"Failed to convert '" + JoseHeaderNames.X5U + "' JOSE header"), ex);
+					builder.x509CertURL(x509Uri.toURI());
+				}
+				catch (Exception ex) {
+					throw new JwtEncodingException(String.format(ENCODING_ERROR_MESSAGE_TEMPLATE,
+							"Failed to convert '" + JoseHeaderNames.X5U + "' JOSE header to a URI"), ex);
 				}
 			}
 
 			Map<String, Object> customHeaders = headers.getHeaders().entrySet().stream()
-					.filter(header -> !JWSHeader.getRegisteredParameterNames().contains(header.getKey()))
+					.filter((header) -> !JWSHeader.getRegisteredParameterNames().contains(header.getKey()))
 					.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
 			if (!CollectionUtils.isEmpty(customHeaders)) {
 				builder.customParams(customHeaders);
@@ -278,6 +273,7 @@ public final class NimbusJwsEncoder implements JwtEncoder {
 
 			return builder.build();
 		}
+
 	}
 
 	private static class JwtClaimsSetConverter implements Converter<JwtClaimsSet, JWTClaimsSet> {
@@ -322,7 +318,7 @@ public final class NimbusJwsEncoder implements JwtEncoder {
 			}
 
 			Map<String, Object> customClaims = claims.getClaims().entrySet().stream()
-					.filter(claim -> !JWTClaimsSet.getRegisteredNames().contains(claim.getKey()))
+					.filter((claim) -> !JWTClaimsSet.getRegisteredNames().contains(claim.getKey()))
 					.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
 			if (!CollectionUtils.isEmpty(customClaims)) {
 				customClaims.forEach(builder::claim);
@@ -330,5 +326,7 @@ public final class NimbusJwsEncoder implements JwtEncoder {
 
 			return builder.build();
 		}
+
 	}
+
 }

+ 2 - 2
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIssuerUtil.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 the original author or authors.
+ * Copyright 2020-2021 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.
@@ -21,7 +21,7 @@ import org.springframework.security.oauth2.core.OAuth2RefreshToken;
 import org.springframework.security.oauth2.core.OAuth2RefreshToken2;
 import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
 import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
-import org.springframework.security.oauth2.jose.JoseHeader;
+import org.springframework.security.oauth2.jwt.JoseHeader;
 import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
 import org.springframework.security.oauth2.jwt.Jwt;
 import org.springframework.security.oauth2.jwt.JwtClaimsSet;

+ 35 - 66
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/JwkSetEndpointFilter.java → oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/NimbusJwkSetEndpointFilter.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 the original author or authors.
+ * Copyright 2020-2021 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.
@@ -15,70 +15,65 @@
  */
 package org.springframework.security.oauth2.server.authorization.web;
 
-import com.nimbusds.jose.JWSAlgorithm;
-import com.nimbusds.jose.jwk.Curve;
-import com.nimbusds.jose.jwk.ECKey;
-import com.nimbusds.jose.jwk.JWK;
+import java.io.IOException;
+import java.io.Writer;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import com.nimbusds.jose.jwk.JWKMatcher;
+import com.nimbusds.jose.jwk.JWKSelector;
 import com.nimbusds.jose.jwk.JWKSet;
-import com.nimbusds.jose.jwk.KeyUse;
-import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.jose.jwk.source.JWKSource;
+import com.nimbusds.jose.proc.SecurityContext;
+
 import org.springframework.http.HttpMethod;
 import org.springframework.http.MediaType;
-import org.springframework.security.crypto.key.AsymmetricKey;
-import org.springframework.security.crypto.key.CryptoKeySource;
 import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 import org.springframework.security.web.util.matcher.RequestMatcher;
 import org.springframework.util.Assert;
 import org.springframework.web.filter.OncePerRequestFilter;
 
-import javax.servlet.FilterChain;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.io.Writer;
-import java.security.interfaces.ECPublicKey;
-import java.security.interfaces.RSAPublicKey;
-import java.util.Objects;
-import java.util.stream.Collectors;
-
 /**
  * A {@code Filter} that processes JWK Set requests.
  *
  * @author Joe Grandja
  * @since 0.0.1
- * @see CryptoKeySource
+ * @see com.nimbusds.jose.jwk.source.JWKSource
  * @see <a target="_blank" href="https://tools.ietf.org/html/rfc7517">JSON Web Key (JWK)</a>
  * @see <a target="_blank" href="https://tools.ietf.org/html/rfc7517#section-5">Section 5 JWK Set Format</a>
  */
-public class JwkSetEndpointFilter extends OncePerRequestFilter {
+public class NimbusJwkSetEndpointFilter extends OncePerRequestFilter {
 	/**
 	 * The default endpoint {@code URI} for JWK Set requests.
 	 */
 	public static final String DEFAULT_JWK_SET_ENDPOINT_URI = "/oauth2/jwks";
 
-	private final CryptoKeySource keySource;
+	private final JWKSource<SecurityContext> jwkSource;
+	private final JWKSelector jwkSelector;
 	private final RequestMatcher requestMatcher;
 
 	/**
-	 * Constructs a {@code JwkSetEndpointFilter} using the provided parameters.
-	 *
-	 * @param keySource the source for cryptographic keys
+	 * Constructs a {@code NimbusJwkSetEndpointFilter} using the provided parameters.
+	 * @param jwkSource the {@code com.nimbusds.jose.jwk.source.JWKSource}
 	 */
-	public JwkSetEndpointFilter(CryptoKeySource keySource) {
-		this(keySource, DEFAULT_JWK_SET_ENDPOINT_URI);
+	public NimbusJwkSetEndpointFilter(JWKSource<SecurityContext> jwkSource) {
+		this(jwkSource, DEFAULT_JWK_SET_ENDPOINT_URI);
 	}
 
 	/**
-	 * Constructs a {@code JwkSetEndpointFilter} using the provided parameters.
+	 * Constructs a {@code NimbusJwkSetEndpointFilter} using the provided parameters.
 	 *
-	 * @param keySource the source for cryptographic keys
+	 * @param jwkSource the {@code com.nimbusds.jose.jwk.source.JWKSource}
 	 * @param jwkSetEndpointUri the endpoint {@code URI} for JWK Set requests
 	 */
-	public JwkSetEndpointFilter(CryptoKeySource keySource, String jwkSetEndpointUri) {
-		Assert.notNull(keySource, "keySource cannot be null");
+	public NimbusJwkSetEndpointFilter(JWKSource<SecurityContext> jwkSource, String jwkSetEndpointUri) {
+		Assert.notNull(jwkSource, "jwkSource cannot be null");
 		Assert.hasText(jwkSetEndpointUri, "jwkSetEndpointUri cannot be empty");
-		this.keySource = keySource;
+		this.jwkSource = jwkSource;
+		this.jwkSelector = new JWKSelector(new JWKMatcher.Builder().publicOnly(true).build());
 		this.requestMatcher = new AntPathRequestMatcher(jwkSetEndpointUri, HttpMethod.GET.name());
 	}
 
@@ -91,43 +86,17 @@ public class JwkSetEndpointFilter extends OncePerRequestFilter {
 			return;
 		}
 
-		JWKSet jwkSet = buildJwkSet();
+		JWKSet jwkSet;
+		try {
+			jwkSet = new JWKSet(this.jwkSource.get(this.jwkSelector, null));
+		}
+		catch (Exception ex) {
+			throw new IllegalStateException("Failed to select the JWK public key(s) -> " + ex.getMessage(), ex);
+		}
 
 		response.setContentType(MediaType.APPLICATION_JSON_VALUE);
 		try (Writer writer = response.getWriter()) {
 			writer.write(jwkSet.toString());
 		}
 	}
-
-	private JWKSet buildJwkSet() {
-		return new JWKSet(
-				this.keySource.getKeys().stream()
-						.filter(key -> AsymmetricKey.class.isAssignableFrom(key.getClass()))
-						.map(AsymmetricKey.class::cast)
-						.map(this::convert)
-						.filter(Objects::nonNull)
-						.collect(Collectors.toList())
-		);
-	}
-
-	private JWK convert(AsymmetricKey asymmetricKey) {
-		JWK jwk = null;
-		if (asymmetricKey.getPublicKey() instanceof RSAPublicKey) {
-			RSAPublicKey publicKey = (RSAPublicKey) asymmetricKey.getPublicKey();
-			jwk = new RSAKey.Builder(publicKey)
-					.keyUse(KeyUse.SIGNATURE)
-					.algorithm(JWSAlgorithm.RS256)
-					.keyID(asymmetricKey.getId())
-					.build();
-		} else if (asymmetricKey.getPublicKey() instanceof ECPublicKey) {
-			ECPublicKey publicKey = (ECPublicKey) asymmetricKey.getPublicKey();
-			Curve curve = Curve.forECParameterSpec(publicKey.getParams());
-			jwk = new ECKey.Builder(curve, publicKey)
-					.keyUse(KeyUse.SIGNATURE)
-					.algorithm(JWSAlgorithm.ES256)
-					.keyID(asymmetricKey.getId())
-					.build();
-		}
-		return jwk;
-	}
 }

+ 19 - 15
oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationCodeGrantTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 the original author or authors.
+ * Copyright 2020-2021 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.
@@ -15,11 +15,20 @@
  */
 package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
 
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.function.BiConsumer;
+
+import com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jose.jwk.source.JWKSource;
+import com.nimbusds.jose.proc.SecurityContext;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
+
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Import;
@@ -27,16 +36,15 @@ import org.springframework.http.HttpHeaders;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
 import org.springframework.security.config.test.SpringTestRule;
-import org.springframework.security.crypto.key.CryptoKeySource;
-import org.springframework.security.crypto.key.StaticKeyGeneratingCryptoKeySource;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
 import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
 import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
 import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
-import org.springframework.security.oauth2.jose.JoseHeader;
-import org.springframework.security.oauth2.jose.jws.NimbusJwsEncoder;
+import org.springframework.security.oauth2.jose.TestJwks;
+import org.springframework.security.oauth2.jwt.JoseHeader;
 import org.springframework.security.oauth2.jwt.JwtClaimsSet;
 import org.springframework.security.oauth2.jwt.JwtEncoder;
+import org.springframework.security.oauth2.jwt.NimbusJwsEncoder;
 import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
 import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
 import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
@@ -54,11 +62,6 @@ import org.springframework.util.LinkedMultiValueMap;
 import org.springframework.util.MultiValueMap;
 import org.springframework.util.StringUtils;
 
-import java.net.URLEncoder;
-import java.nio.charset.StandardCharsets;
-import java.util.Base64;
-import java.util.function.BiConsumer;
-
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.mockito.ArgumentMatchers.any;
@@ -90,7 +93,7 @@ public class OAuth2AuthorizationCodeGrantTests {
 
 	private static RegisteredClientRepository registeredClientRepository;
 	private static OAuth2AuthorizationService authorizationService;
-	private static CryptoKeySource keySource;
+	private static JWKSource<SecurityContext> jwkSource;
 	private static NimbusJwsEncoder jwtEncoder;
 	private static BiConsumer<JoseHeader.Builder, JwtClaimsSet.Builder> jwtCustomizer;
 
@@ -104,8 +107,9 @@ public class OAuth2AuthorizationCodeGrantTests {
 	public static void init() {
 		registeredClientRepository = mock(RegisteredClientRepository.class);
 		authorizationService = mock(OAuth2AuthorizationService.class);
-		keySource = new StaticKeyGeneratingCryptoKeySource();
-		jwtEncoder = new NimbusJwsEncoder(keySource);
+		JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK);
+		jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
+		jwtEncoder = new NimbusJwsEncoder(jwkSource);
 		jwtCustomizer = mock(BiConsumer.class);
 		jwtEncoder.setJwtCustomizer(jwtCustomizer);
 	}
@@ -298,8 +302,8 @@ public class OAuth2AuthorizationCodeGrantTests {
 		}
 
 		@Bean
-		CryptoKeySource keySource() {
-			return keySource;
+		JWKSource<SecurityContext> jwkSource() {
+			return jwkSource;
 		}
 	}
 

+ 15 - 11
oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ClientCredentialsGrantTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 the original author or authors.
+ * Copyright 2020-2021 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.
@@ -15,10 +15,18 @@
  */
 package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
 
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+import com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jose.jwk.source.JWKSource;
+import com.nimbusds.jose.proc.SecurityContext;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
+
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Import;
@@ -26,10 +34,9 @@ import org.springframework.http.HttpHeaders;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
 import org.springframework.security.config.test.SpringTestRule;
-import org.springframework.security.crypto.key.CryptoKeySource;
-import org.springframework.security.crypto.key.StaticKeyGeneratingCryptoKeySource;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
 import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.security.oauth2.jose.TestJwks;
 import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
@@ -38,10 +45,6 @@ import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenE
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
 
-import java.net.URLEncoder;
-import java.nio.charset.StandardCharsets;
-import java.util.Base64;
-
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
@@ -61,7 +64,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
 public class OAuth2ClientCredentialsGrantTests {
 	private static RegisteredClientRepository registeredClientRepository;
 	private static OAuth2AuthorizationService authorizationService;
-	private static CryptoKeySource keySource;
+	private static JWKSource<SecurityContext> jwkSource;
 
 	@Rule
 	public final SpringTestRule spring = new SpringTestRule();
@@ -73,7 +76,8 @@ public class OAuth2ClientCredentialsGrantTests {
 	public static void init() {
 		registeredClientRepository = mock(RegisteredClientRepository.class);
 		authorizationService = mock(OAuth2AuthorizationService.class);
-		keySource = new StaticKeyGeneratingCryptoKeySource();
+		JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK);
+		jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
 	}
 
 	@Before
@@ -159,8 +163,8 @@ public class OAuth2ClientCredentialsGrantTests {
 		}
 
 		@Bean
-		CryptoKeySource keySource() {
-			return keySource;
+		JWKSource<SecurityContext> jwkSource() {
+			return jwkSource;
 		}
 	}
 }

+ 15 - 11
oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2RefreshTokenGrantTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 the original author or authors.
+ * Copyright 2020-2021 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.
@@ -15,10 +15,18 @@
  */
 package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
 
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+import com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jose.jwk.source.JWKSource;
+import com.nimbusds.jose.proc.SecurityContext;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
+
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Import;
@@ -26,10 +34,9 @@ import org.springframework.http.HttpHeaders;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
 import org.springframework.security.config.test.SpringTestRule;
-import org.springframework.security.crypto.key.CryptoKeySource;
-import org.springframework.security.crypto.key.StaticKeyGeneratingCryptoKeySource;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
 import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.security.oauth2.jose.TestJwks;
 import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
 import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
 import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
@@ -42,10 +49,6 @@ import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.util.LinkedMultiValueMap;
 import org.springframework.util.MultiValueMap;
 
-import java.net.URLEncoder;
-import java.nio.charset.StandardCharsets;
-import java.util.Base64;
-
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
@@ -67,7 +70,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
 public class OAuth2RefreshTokenGrantTests {
 	private static RegisteredClientRepository registeredClientRepository;
 	private static OAuth2AuthorizationService authorizationService;
-	private static CryptoKeySource keySource;
+	private static JWKSource<SecurityContext> jwkSource;
 
 	@Rule
 	public final SpringTestRule spring = new SpringTestRule();
@@ -79,7 +82,8 @@ public class OAuth2RefreshTokenGrantTests {
 	public static void init() {
 		registeredClientRepository = mock(RegisteredClientRepository.class);
 		authorizationService = mock(OAuth2AuthorizationService.class);
-		keySource = new StaticKeyGeneratingCryptoKeySource();
+		JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK);
+		jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
 	}
 
 	@Before
@@ -153,8 +157,8 @@ public class OAuth2RefreshTokenGrantTests {
 		}
 
 		@Bean
-		CryptoKeySource keySource() {
-			return keySource;
+		JWKSource<SecurityContext> jwkSource() {
+			return jwkSource;
 		}
 	}
 }

+ 15 - 11
oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2TokenRevocationTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 the original author or authors.
+ * Copyright 2020-2021 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.
@@ -15,11 +15,19 @@
  */
 package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
 
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+import com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jose.jwk.source.JWKSource;
+import com.nimbusds.jose.proc.SecurityContext;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
+
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Import;
@@ -27,12 +35,11 @@ import org.springframework.http.HttpHeaders;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
 import org.springframework.security.config.test.SpringTestRule;
-import org.springframework.security.crypto.key.CryptoKeySource;
-import org.springframework.security.crypto.key.StaticKeyGeneratingCryptoKeySource;
 import org.springframework.security.oauth2.core.AbstractOAuth2Token;
 import org.springframework.security.oauth2.core.OAuth2AccessToken;
 import org.springframework.security.oauth2.core.OAuth2RefreshToken;
 import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames2;
+import org.springframework.security.oauth2.jose.TestJwks;
 import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
 import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
 import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
@@ -46,10 +53,6 @@ import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
 import org.springframework.util.LinkedMultiValueMap;
 import org.springframework.util.MultiValueMap;
 
-import java.net.URLEncoder;
-import java.nio.charset.StandardCharsets;
-import java.util.Base64;
-
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
@@ -66,7 +69,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
 public class OAuth2TokenRevocationTests {
 	private static RegisteredClientRepository registeredClientRepository;
 	private static OAuth2AuthorizationService authorizationService;
-	private static CryptoKeySource keySource;
+	private static JWKSource<SecurityContext> jwkSource;
 
 	@Rule
 	public final SpringTestRule spring = new SpringTestRule();
@@ -78,7 +81,8 @@ public class OAuth2TokenRevocationTests {
 	public static void init() {
 		registeredClientRepository = mock(RegisteredClientRepository.class);
 		authorizationService = mock(OAuth2AuthorizationService.class);
-		keySource = new StaticKeyGeneratingCryptoKeySource();
+		JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK);
+		jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
 	}
 
 	@Before
@@ -181,8 +185,8 @@ public class OAuth2TokenRevocationTests {
 		}
 
 		@Bean
-		CryptoKeySource keySource() {
-			return keySource;
+		JWKSource<SecurityContext> jwkSource() {
+			return jwkSource;
 		}
 	}
 }

+ 16 - 12
oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 the original author or authors.
+ * Copyright 2020-2021 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.
@@ -15,11 +15,19 @@
  */
 package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
 
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+import com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jose.jwk.source.JWKSource;
+import com.nimbusds.jose.proc.SecurityContext;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
+
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Import;
@@ -27,12 +35,11 @@ import org.springframework.http.HttpHeaders;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
 import org.springframework.security.config.test.SpringTestRule;
-import org.springframework.security.crypto.key.CryptoKeySource;
-import org.springframework.security.crypto.key.StaticKeyGeneratingCryptoKeySource;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
 import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
 import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
 import org.springframework.security.oauth2.core.oidc.OidcScopes;
+import org.springframework.security.oauth2.jose.TestJwks;
 import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
 import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
 import org.springframework.security.oauth2.server.authorization.TokenType;
@@ -40,20 +47,16 @@ import org.springframework.security.oauth2.server.authorization.client.Registere
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
 import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
 import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
+import org.springframework.security.oauth2.server.authorization.oidc.web.OidcProviderConfigurationEndpointFilter;
 import org.springframework.security.oauth2.server.authorization.token.OAuth2AuthorizationCode;
 import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter;
 import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter;
-import org.springframework.security.oauth2.server.authorization.oidc.web.OidcProviderConfigurationEndpointFilter;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.MvcResult;
 import org.springframework.util.LinkedMultiValueMap;
 import org.springframework.util.MultiValueMap;
 import org.springframework.util.StringUtils;
 
-import java.net.URLEncoder;
-import java.nio.charset.StandardCharsets;
-import java.util.Base64;
-
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.hamcrest.CoreMatchers.containsString;
@@ -80,7 +83,7 @@ public class OidcTests {
 	private static final String issuerUrl = "https://example.com/issuer1";
 	private static RegisteredClientRepository registeredClientRepository;
 	private static OAuth2AuthorizationService authorizationService;
-	private static CryptoKeySource keySource;
+	private static JWKSource<SecurityContext> jwkSource;
 
 	@Rule
 	public final SpringTestRule spring = new SpringTestRule();
@@ -92,7 +95,8 @@ public class OidcTests {
 	public static void init() {
 		registeredClientRepository = mock(RegisteredClientRepository.class);
 		authorizationService = mock(OAuth2AuthorizationService.class);
-		keySource = new StaticKeyGeneratingCryptoKeySource();
+		JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK);
+		jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
 	}
 
 	@Before
@@ -224,8 +228,8 @@ public class OidcTests {
 		}
 
 		@Bean
-		CryptoKeySource keySource() {
-			return keySource;
+		JWKSource<SecurityContext> jwkSource() {
+			return jwkSource;
 		}
 	}
 

+ 0 - 113
oauth2-authorization-server/src/test/java/org/springframework/security/crypto/key/CryptoKeyTests.java

@@ -1,113 +0,0 @@
-/*
- * Copyright 2020 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.crypto.key;
-
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-import javax.crypto.SecretKey;
-import java.security.KeyPair;
-import java.util.HashMap;
-import java.util.Map;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.springframework.security.crypto.key.KeyGeneratorUtils.generateRsaKey;
-import static org.springframework.security.crypto.key.KeyGeneratorUtils.generateSecretKey;
-
-/**
- * Tests for {@link CryptoKey}.
- *
- * @author Joe Grandja
- */
-public class CryptoKeyTests {
-	private static SecretKey secretKey;
-	private static KeyPair rsaKeyPair;
-
-	@BeforeClass
-	public static void init() {
-		secretKey = generateSecretKey();
-		rsaKeyPair = generateRsaKey();
-	}
-
-	@Test
-	public void symmetricWhenSecretKeyNullThenThrowIllegalArgumentException() {
-		assertThatThrownBy(() -> CryptoKey.symmetric(null))
-				.isInstanceOf(IllegalArgumentException.class)
-				.hasMessage("key cannot be null");
-	}
-
-	@Test
-	public void metadataWhenNameNullThenThrowIllegalArgumentException() {
-		assertThatThrownBy(() -> CryptoKey.symmetric(secretKey).metadata(null, "value"))
-				.isInstanceOf(IllegalArgumentException.class)
-				.hasMessage("name cannot be empty");
-	}
-
-	@Test
-	public void metadataWhenValueNullThenThrowIllegalArgumentException() {
-		assertThatThrownBy(() -> CryptoKey.symmetric(secretKey).metadata("name", null))
-				.isInstanceOf(IllegalArgumentException.class)
-				.hasMessage("value cannot be null");
-	}
-
-	@Test
-	public void symmetricWhenAllAttributesProvidedThenAllAttributesAreSet() {
-		Map<String, Object> keyMetadata = new HashMap<>();
-		keyMetadata.put("name1", "value1");
-		keyMetadata.put("name2", "value2");
-
-		SymmetricKey symmetricKey = CryptoKey.symmetric(secretKey)
-				.id("id")
-				.metadata(metadata -> metadata.putAll(keyMetadata))
-				.build();
-
-		assertThat(symmetricKey.getKey()).isEqualTo(secretKey);
-		assertThat(symmetricKey.getId()).isEqualTo("id");
-		assertThat(symmetricKey.getMetadata()).isEqualTo(keyMetadata);
-	}
-
-	@Test
-	public void asymmetricWhenPrivateKeyNullThenThrowIllegalArgumentException() {
-		assertThatThrownBy(() -> CryptoKey.asymmetric(null, rsaKeyPair.getPublic()))
-				.isInstanceOf(IllegalArgumentException.class)
-				.hasMessage("key cannot be null");
-	}
-
-	@Test
-	public void asymmetricWhenPublicKeyNullThenThrowIllegalArgumentException() {
-		assertThatThrownBy(() -> CryptoKey.asymmetric(rsaKeyPair.getPrivate(), null))
-				.isInstanceOf(IllegalArgumentException.class)
-				.hasMessage("publicKey cannot be null");
-	}
-
-	@Test
-	public void asymmetricWhenAllAttributesProvidedThenAllAttributesAreSet() {
-		Map<String, Object> keyMetadata = new HashMap<>();
-		keyMetadata.put("name1", "value1");
-		keyMetadata.put("name2", "value2");
-
-		AsymmetricKey asymmetricKey = CryptoKey.asymmetric(rsaKeyPair.getPrivate(), rsaKeyPair.getPublic())
-				.id("id")
-				.metadata(metadata -> metadata.putAll(keyMetadata))
-				.build();
-
-		assertThat(asymmetricKey.getKey()).isEqualTo(rsaKeyPair.getPrivate());
-		assertThat(asymmetricKey.getPublicKey()).isEqualTo(rsaKeyPair.getPublic());
-		assertThat(asymmetricKey.getId()).isEqualTo("id");
-		assertThat(asymmetricKey.getMetadata()).isEqualTo(keyMetadata);
-	}
-}

+ 0 - 42
oauth2-authorization-server/src/test/java/org/springframework/security/crypto/key/TestCryptoKeys.java

@@ -1,42 +0,0 @@
-/*
- * Copyright 2020 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.crypto.key;
-
-import java.security.KeyPair;
-
-import static org.springframework.security.crypto.key.KeyGeneratorUtils.generateEcKey;
-import static org.springframework.security.crypto.key.KeyGeneratorUtils.generateRsaKey;
-import static org.springframework.security.crypto.key.KeyGeneratorUtils.generateSecretKey;
-
-/**
- * @author Joe Grandja
- */
-public class TestCryptoKeys {
-
-	public static SymmetricKey.Builder secretKey() {
-		return CryptoKey.symmetric(generateSecretKey());
-	}
-
-	public static AsymmetricKey.Builder rsaKey() {
-		KeyPair rsaKeyPair = generateRsaKey();
-		return CryptoKey.asymmetric(rsaKeyPair.getPrivate(), rsaKeyPair.getPublic());
-	}
-
-	public static AsymmetricKey.Builder ecKey() {
-		KeyPair ecKeyPair = generateEcKey();
-		return CryptoKey.asymmetric(ecKeyPair.getPrivate(), ecKeyPair.getPublic());
-	}
-}

+ 89 - 0
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/jose/TestJwks.java

@@ -0,0 +1,89 @@
+/*
+ * Copyright 2020-2021 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.jose;
+
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+
+import javax.crypto.SecretKey;
+
+import com.nimbusds.jose.jwk.Curve;
+import com.nimbusds.jose.jwk.ECKey;
+import com.nimbusds.jose.jwk.KeyUse;
+import com.nimbusds.jose.jwk.OctetSequenceKey;
+import com.nimbusds.jose.jwk.RSAKey;
+
+/**
+ * @author Joe Grandja
+ */
+public final class TestJwks {
+
+	// @formatter:off
+	public static final RSAKey DEFAULT_RSA_JWK =
+			jwk(
+					TestKeys.DEFAULT_PUBLIC_KEY,
+					TestKeys.DEFAULT_PRIVATE_KEY
+			).build();
+	// @formatter:on
+
+	// @formatter:off
+	public static final ECKey DEFAULT_EC_JWK =
+			jwk(
+					(ECPublicKey) TestKeys.DEFAULT_EC_KEY_PAIR.getPublic(),
+					(ECPrivateKey) TestKeys.DEFAULT_EC_KEY_PAIR.getPrivate()
+			).build();
+	// @formatter:on
+
+	// @formatter:off
+	public static final OctetSequenceKey DEFAULT_SECRET_JWK =
+			jwk(
+					TestKeys.DEFAULT_SECRET_KEY
+			).build();
+	// @formatter:on
+
+	private TestJwks() {
+	}
+
+	public static RSAKey.Builder jwk(RSAPublicKey publicKey, RSAPrivateKey privateKey) {
+		// @formatter:off
+		return new RSAKey.Builder(publicKey)
+				.privateKey(privateKey)
+				.keyUse(KeyUse.SIGNATURE)
+				.keyID("rsa-jwk-kid");
+		// @formatter:on
+	}
+
+	public static ECKey.Builder jwk(ECPublicKey publicKey, ECPrivateKey privateKey) {
+		// @formatter:off
+		Curve curve = Curve.forECParameterSpec(publicKey.getParams());
+		return new ECKey.Builder(curve, publicKey)
+				.privateKey(privateKey)
+				.keyUse(KeyUse.SIGNATURE)
+				.keyID("ec-jwk-kid");
+		// @formatter:on
+	}
+
+	public static OctetSequenceKey.Builder jwk(SecretKey secretKey) {
+		// @formatter:off
+		return new OctetSequenceKey.Builder(secretKey)
+				.keyUse(KeyUse.SIGNATURE)
+				.keyID("secret-jwk-kid");
+		// @formatter:on
+	}
+
+}

+ 154 - 0
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/jose/TestKeys.java

@@ -0,0 +1,154 @@
+/*
+ * Copyright 2002-2021 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.jose;
+
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.ECFieldFp;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+import java.security.spec.EllipticCurve;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Base64;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * TODO
+ * This class is a straight copy from Spring Security.
+ * It should be removed when merging this codebase into Spring Security.
+ *
+ * @author Joe Grandja
+ * @since 5.2
+ */
+public final class TestKeys {
+
+	public static final KeyFactory kf;
+	static {
+		try {
+			kf = KeyFactory.getInstance("RSA");
+		}
+		catch (NoSuchAlgorithmException ex) {
+			throw new IllegalStateException(ex);
+		}
+	}
+	public static final String DEFAULT_ENCODED_SECRET_KEY = "bCzY/M48bbkwBEWjmNSIEPfwApcvXOnkCxORBEbPr+4=";
+
+	public static final SecretKey DEFAULT_SECRET_KEY = new SecretKeySpec(
+			Base64.getDecoder().decode(DEFAULT_ENCODED_SECRET_KEY), "AES");
+
+	// @formatter:off
+	public static final String DEFAULT_RSA_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3FlqJr5TRskIQIgdE3Dd"
+			+ "7D9lboWdcTUT8a+fJR7MAvQm7XXNoYkm3v7MQL1NYtDvL2l8CAnc0WdSTINU6IRv"
+			+ "c5Kqo2Q4csNX9SHOmEfzoROjQqahEcve1jBXluoCXdYuYpx4/1tfRgG6ii4Uhxh6"
+			+ "iI8qNMJQX+fLfqhbfYfxBQVRPywBkAbIP4x1EAsbC6FSNmkhCxiMNqEgxaIpY8C2"
+			+ "kJdJ/ZIV+WW4noDdzpKqHcwmB8FsrumlVY/DNVvUSDIipiq9PbP4H99TXN1o746o"
+			+ "RaNa07rq1hoCgMSSy+85SagCoxlmyE+D+of9SsMY8Ol9t0rdzpobBuhyJ/o5dfvj"
+			+ "KwIDAQAB";
+	// @formatter:on
+
+	public static final RSAPublicKey DEFAULT_PUBLIC_KEY;
+	static {
+		X509EncodedKeySpec spec = new X509EncodedKeySpec(Base64.getDecoder().decode(DEFAULT_RSA_PUBLIC_KEY));
+		try {
+			DEFAULT_PUBLIC_KEY = (RSAPublicKey) kf.generatePublic(spec);
+		}
+		catch (InvalidKeySpecException ex) {
+			throw new IllegalArgumentException(ex);
+		}
+	}
+
+	// @formatter:off
+	public static final String DEFAULT_RSA_PRIVATE_KEY = "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDcWWomvlNGyQhA"
+			+ "iB0TcN3sP2VuhZ1xNRPxr58lHswC9Cbtdc2hiSbe/sxAvU1i0O8vaXwICdzRZ1JM"
+			+ "g1TohG9zkqqjZDhyw1f1Ic6YR/OhE6NCpqERy97WMFeW6gJd1i5inHj/W19GAbqK"
+			+ "LhSHGHqIjyo0wlBf58t+qFt9h/EFBVE/LAGQBsg/jHUQCxsLoVI2aSELGIw2oSDF"
+			+ "oiljwLaQl0n9khX5ZbiegN3OkqodzCYHwWyu6aVVj8M1W9RIMiKmKr09s/gf31Nc"
+			+ "3WjvjqhFo1rTuurWGgKAxJLL7zlJqAKjGWbIT4P6h/1Kwxjw6X23St3OmhsG6HIn"
+			+ "+jl1++MrAgMBAAECggEBAMf820wop3pyUOwI3aLcaH7YFx5VZMzvqJdNlvpg1jbE"
+			+ "E2Sn66b1zPLNfOIxLcBG8x8r9Ody1Bi2Vsqc0/5o3KKfdgHvnxAB3Z3dPh2WCDek"
+			+ "lCOVClEVoLzziTuuTdGO5/CWJXdWHcVzIjPxmK34eJXioiLaTYqN3XKqKMdpD0ZG"
+			+ "mtNTGvGf+9fQ4i94t0WqIxpMpGt7NM4RHy3+Onggev0zLiDANC23mWrTsUgect/7"
+			+ "62TYg8g1bKwLAb9wCBT+BiOuCc2wrArRLOJgUkj/F4/gtrR9ima34SvWUyoUaKA0"
+			+ "bi4YBX9l8oJwFGHbU9uFGEMnH0T/V0KtIB7qetReywkCgYEA9cFyfBIQrYISV/OA"
+			+ "+Z0bo3vh2aL0QgKrSXZ924cLt7itQAHNZ2ya+e3JRlTczi5mnWfjPWZ6eJB/8MlH"
+			+ "Gpn12o/POEkU+XjZZSPe1RWGt5g0S3lWqyx9toCS9ACXcN9tGbaqcFSVI73zVTRA"
+			+ "8J9grR0fbGn7jaTlTX2tnlOTQ60CgYEA5YjYpEq4L8UUMFkuj+BsS3u0oEBnzuHd"
+			+ "I9LEHmN+CMPosvabQu5wkJXLuqo2TxRnAznsA8R3pCLkdPGoWMCiWRAsCn979TdY"
+			+ "QbqO2qvBAD2Q19GtY7lIu6C35/enQWzJUMQE3WW0OvjLzZ0l/9mA2FBRR+3F9A1d"
+			+ "rBdnmv0c3TcCgYEAi2i+ggVZcqPbtgrLOk5WVGo9F1GqUBvlgNn30WWNTx4zIaEk"
+			+ "HSxtyaOLTxtq2odV7Kr3LGiKxwPpn/T+Ief+oIp92YcTn+VfJVGw4Z3BezqbR8lA"
+			+ "Uf/+HF5ZfpMrVXtZD4Igs3I33Duv4sCuqhEvLWTc44pHifVloozNxYfRfU0CgYBN"
+			+ "HXa7a6cJ1Yp829l62QlJKtx6Ymj95oAnQu5Ez2ROiZMqXRO4nucOjGUP55Orac1a"
+			+ "FiGm+mC/skFS0MWgW8evaHGDbWU180wheQ35hW6oKAb7myRHtr4q20ouEtQMdQIF"
+			+ "snV39G1iyqeeAsf7dxWElydXpRi2b68i3BIgzhzebQKBgQCdUQuTsqV9y/JFpu6H"
+			+ "c5TVvhG/ubfBspI5DhQqIGijnVBzFT//UfIYMSKJo75qqBEyP2EJSmCsunWsAFsM"
+			+ "TszuiGTkrKcZy9G0wJqPztZZl2F2+bJgnA6nBEV7g5PA4Af+QSmaIhRwqGDAuROR"
+			+ "47jndeyIaMTNETEmOnms+as17g==";
+	// @formatter:on
+
+	public static final RSAPrivateKey DEFAULT_PRIVATE_KEY;
+	static {
+		PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(DEFAULT_RSA_PRIVATE_KEY));
+		try {
+			DEFAULT_PRIVATE_KEY = (RSAPrivateKey) kf.generatePrivate(spec);
+		}
+		catch (InvalidKeySpecException ex) {
+			throw new IllegalArgumentException(ex);
+		}
+	}
+
+	public static final KeyPair DEFAULT_RSA_KEY_PAIR = new KeyPair(DEFAULT_PUBLIC_KEY, DEFAULT_PRIVATE_KEY);
+
+	public static final KeyPair DEFAULT_EC_KEY_PAIR = generateEcKeyPair();
+
+	static KeyPair generateEcKeyPair() {
+		EllipticCurve ellipticCurve = new EllipticCurve(
+				new ECFieldFp(new BigInteger(
+						"115792089210356248762697446949407573530086143415290314195533631308867097853951")),
+				new BigInteger("115792089210356248762697446949407573530086143415290314195533631308867097853948"),
+				new BigInteger("41058363725152142129326129780047268409114441015993725554835256314039467401291"));
+		ECPoint ecPoint = new ECPoint(
+				new BigInteger("48439561293906451759052585252797914202762949526041747995844080717082404635286"),
+				new BigInteger("36134250956749795798585127919587881956611106672985015071877198253568414405109"));
+		ECParameterSpec ecParameterSpec = new ECParameterSpec(ellipticCurve, ecPoint,
+				new BigInteger("115792089210356248762697446949407573529996955224135760342422259061068512044369"), 1);
+
+		KeyPair keyPair;
+		try {
+			KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
+			keyPairGenerator.initialize(ecParameterSpec);
+			keyPair = keyPairGenerator.generateKeyPair();
+		}
+		catch (Exception ex) {
+			throw new IllegalStateException(ex);
+		}
+		return keyPair;
+	}
+
+	private TestKeys() {
+	}
+
+}

+ 0 - 177
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/jose/jws/NimbusJwsEncoderTests.java

@@ -1,177 +0,0 @@
-/*
- * Copyright 2020 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.jose.jws;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.springframework.security.crypto.key.AsymmetricKey;
-import org.springframework.security.crypto.key.CryptoKeySource;
-import org.springframework.security.crypto.key.TestCryptoKeys;
-import org.springframework.security.oauth2.jose.JoseHeader;
-import org.springframework.security.oauth2.jose.JoseHeaderNames;
-import org.springframework.security.oauth2.jose.TestJoseHeaders;
-import org.springframework.security.oauth2.jwt.Jwt;
-import org.springframework.security.oauth2.jwt.JwtClaimsSet;
-import org.springframework.security.oauth2.jwt.JwtEncodingException;
-import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
-import org.springframework.security.oauth2.jwt.TestJwtClaimsSets;
-
-import java.security.interfaces.RSAPublicKey;
-import java.util.Collections;
-import java.util.LinkedHashSet;
-import java.util.function.BiConsumer;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-/**
- * Tests for {@link NimbusJwsEncoder}.
- *
- * @author Joe Grandja
- */
-public class NimbusJwsEncoderTests {
-	private CryptoKeySource keySource;
-	private NimbusJwsEncoder jwtEncoder;
-
-	@Before
-	public void setUp() {
-		this.keySource = mock(CryptoKeySource.class);
-		this.jwtEncoder = new NimbusJwsEncoder(this.keySource);
-	}
-
-	@Test
-	public void constructorWhenKeySourceNullThenThrowIllegalArgumentException() {
-		assertThatThrownBy(() -> new NimbusJwsEncoder(null))
-				.isInstanceOf(IllegalArgumentException.class)
-				.hasMessage("keySource cannot be null");
-	}
-
-	@Test
-	public void setJwtCustomizerWhenNullThenThrowIllegalArgumentException() {
-		assertThatThrownBy(() -> this.jwtEncoder.setJwtCustomizer(null))
-				.isInstanceOf(IllegalArgumentException.class)
-				.hasMessage("jwtCustomizer cannot be null");
-	}
-
-	@Test
-	public void encodeWhenHeadersNullThenThrowIllegalArgumentException() {
-		JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet().build();
-
-		assertThatThrownBy(() -> this.jwtEncoder.encode(null, jwtClaimsSet))
-				.isInstanceOf(IllegalArgumentException.class)
-				.hasMessage("headers cannot be null");
-	}
-
-	@Test
-	public void encodeWhenClaimsNullThenThrowIllegalArgumentException() {
-		JoseHeader joseHeader = TestJoseHeaders.joseHeader().build();
-
-		assertThatThrownBy(() -> this.jwtEncoder.encode(joseHeader, null))
-				.isInstanceOf(IllegalArgumentException.class)
-				.hasMessage("claims cannot be null");
-	}
-
-	@Test
-	public void encodeWhenUnsupportedKeyThenThrowJwtEncodingException() {
-		JoseHeader joseHeader = TestJoseHeaders.joseHeader().build();
-		JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet().build();
-
-		assertThatThrownBy(() -> this.jwtEncoder.encode(joseHeader, jwtClaimsSet))
-				.isInstanceOf(JwtEncodingException.class)
-				.hasMessageContaining("Unsupported key for algorithm 'RS256'");
-	}
-
-	@Test
-	public void encodeWhenUnsupportedKeyAlgorithmThenThrowJwtEncodingException() {
-		JoseHeader joseHeader = TestJoseHeaders.joseHeader(SignatureAlgorithm.ES256).build();
-		JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet().build();
-
-		assertThatThrownBy(() -> this.jwtEncoder.encode(joseHeader, jwtClaimsSet))
-				.isInstanceOf(JwtEncodingException.class)
-				.hasMessageContaining("Unsupported key for algorithm 'ES256'");
-	}
-
-	@Test
-	public void encodeWhenUnsupportedKeyTypeThenThrowJwtEncodingException() {
-		AsymmetricKey ecKey = TestCryptoKeys.ecKey().build();
-		when(this.keySource.getKeys()).thenReturn(Collections.singleton(ecKey));
-
-		JoseHeader joseHeader = TestJoseHeaders.joseHeader(SignatureAlgorithm.ES256).build();
-		JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet().build();
-
-		assertThatThrownBy(() -> this.jwtEncoder.encode(joseHeader, jwtClaimsSet))
-				.isInstanceOf(JwtEncodingException.class)
-				.hasMessageContaining("Unsupported key type 'EC'");
-	}
-
-	@Test
-	public void encodeWhenSuccessThenDecodes() {
-		AsymmetricKey rsaKey = TestCryptoKeys.rsaKey().build();
-		when(this.keySource.getKeys()).thenReturn(Collections.singleton(rsaKey));
-
-		JoseHeader joseHeader = TestJoseHeaders.joseHeader()
-				.headers(headers -> headers.remove(JoseHeaderNames.CRIT))
-				.build();
-		JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet().build();
-
-		Jwt jws = this.jwtEncoder.encode(joseHeader, jwtClaimsSet);
-
-		NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withPublicKey((RSAPublicKey) rsaKey.getPublicKey()).build();
-		jwtDecoder.decode(jws.getTokenValue());
-	}
-
-	@Test
-	public void encodeWhenCustomizerSetThenCalled() {
-		AsymmetricKey rsaKey = TestCryptoKeys.rsaKey().build();
-		when(this.keySource.getKeys()).thenReturn(Collections.singleton(rsaKey));
-
-		JoseHeader joseHeader = TestJoseHeaders.joseHeader()
-				.headers(headers -> headers.remove(JoseHeaderNames.CRIT))
-				.build();
-		JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet().build();
-
-		BiConsumer<JoseHeader.Builder, JwtClaimsSet.Builder> jwtCustomizer = mock(BiConsumer.class);
-		this.jwtEncoder.setJwtCustomizer(jwtCustomizer);
-
-		this.jwtEncoder.encode(joseHeader, jwtClaimsSet);
-
-		verify(jwtCustomizer).accept(any(JoseHeader.Builder.class), any(JwtClaimsSet.Builder.class));
-	}
-
-	@Test
-	public void encodeWhenMultipleActiveKeysThenUseFirst() {
-		AsymmetricKey rsaKey1 = TestCryptoKeys.rsaKey().build();
-		AsymmetricKey rsaKey2 = TestCryptoKeys.rsaKey().build();
-		when(this.keySource.getKeys()).thenReturn(
-				Stream.of(rsaKey1, rsaKey2)
-						.collect(Collectors.toCollection(LinkedHashSet::new)));
-
-		JoseHeader joseHeader = TestJoseHeaders.joseHeader()
-				.headers(headers -> headers.remove(JoseHeaderNames.CRIT))
-				.build();
-		JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet().build();
-
-		Jwt jws = this.jwtEncoder.encode(joseHeader, jwtClaimsSet);
-
-		NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withPublicKey((RSAPublicKey) rsaKey1.getPublicKey()).build();
-		jwtDecoder.decode(jws.getTokenValue());
-	}
-}

+ 5 - 5
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/jose/JoseHeaderTests.java → oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/jwt/JoseHeaderTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 the original author or authors.
+ * Copyright 2020-2021 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.
@@ -13,9 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.springframework.security.oauth2.jose;
+package org.springframework.security.oauth2.jwt;
 
 import org.junit.Test;
+
 import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
 
 import static org.assertj.core.api.Assertions.assertThat;
@@ -40,14 +41,13 @@ public class JoseHeaderTests {
 		JoseHeader expectedJoseHeader = TestJoseHeaders.joseHeader().build();
 
 		JoseHeader joseHeader = JoseHeader.withAlgorithm(expectedJoseHeader.getJwsAlgorithm())
-				.jwkSetUri(expectedJoseHeader.getJwkSetUri())
+				.jwkSetUri(expectedJoseHeader.getJwkSetUri().toExternalForm())
 				.jwk(expectedJoseHeader.getJwk())
 				.keyId(expectedJoseHeader.getKeyId())
-				.x509Uri(expectedJoseHeader.getX509Uri())
+				.x509Uri(expectedJoseHeader.getX509Uri().toExternalForm())
 				.x509CertificateChain(expectedJoseHeader.getX509CertificateChain())
 				.x509SHA1Thumbprint(expectedJoseHeader.getX509SHA1Thumbprint())
 				.x509SHA256Thumbprint(expectedJoseHeader.getX509SHA256Thumbprint())
-				.critical(expectedJoseHeader.getCritical())
 				.type(expectedJoseHeader.getType())
 				.contentType(expectedJoseHeader.getContentType())
 				.headers(headers -> headers.put("custom-header-name", "custom-header-value"))

+ 291 - 0
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwsEncoderTests.java

@@ -0,0 +1,291 @@
+/*
+ * Copyright 2020-2021 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.jwt;
+
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.BiConsumer;
+
+import com.nimbusds.jose.KeySourceException;
+import com.nimbusds.jose.jwk.ECKey;
+import com.nimbusds.jose.jwk.JWK;
+import com.nimbusds.jose.jwk.JWKSelector;
+import com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jose.jwk.KeyUse;
+import com.nimbusds.jose.jwk.OctetSequenceKey;
+import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.jose.jwk.source.JWKSource;
+import com.nimbusds.jose.proc.SecurityContext;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import org.springframework.security.oauth2.jose.TestJwks;
+import org.springframework.security.oauth2.jose.TestKeys;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.BDDMockito.willAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+/**
+ * Tests for {@link NimbusJwsEncoder}.
+ *
+ * @author Joe Grandja
+ */
+public class NimbusJwsEncoderTests {
+
+	private JWKSource<SecurityContext> jwkSource;
+
+	private NimbusJwsEncoder jwsEncoder;
+
+	@Before
+	public void setUp() {
+		this.jwkSource = mock(JWKSource.class);
+		this.jwsEncoder = new NimbusJwsEncoder(this.jwkSource);
+	}
+
+	@Test
+	public void constructorWhenJwkSourceNullThenThrowIllegalArgumentException() {
+		assertThatIllegalArgumentException().isThrownBy(() -> new NimbusJwsEncoder(null))
+				.withMessage("jwkSource cannot be null");
+	}
+
+	@Test
+	public void setJwtCustomizerWhenNullThenThrowIllegalArgumentException() {
+		assertThatIllegalArgumentException().isThrownBy(() -> this.jwsEncoder.setJwtCustomizer(null))
+				.withMessage("jwtCustomizer cannot be null");
+	}
+
+	@Test
+	public void encodeWhenHeadersNullThenThrowIllegalArgumentException() {
+		JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet().build();
+
+		assertThatIllegalArgumentException().isThrownBy(() -> this.jwsEncoder.encode(null, jwtClaimsSet))
+				.withMessage("headers cannot be null");
+	}
+
+	@Test
+	public void encodeWhenClaimsNullThenThrowIllegalArgumentException() {
+		JoseHeader joseHeader = TestJoseHeaders.joseHeader().build();
+
+		assertThatIllegalArgumentException().isThrownBy(() -> this.jwsEncoder.encode(joseHeader, null))
+				.withMessage("claims cannot be null");
+	}
+
+	@Test
+	public void encodeWhenCustomizerSetThenCalled() throws Exception {
+		RSAKey rsaJwk = TestJwks.DEFAULT_RSA_JWK;
+		given(this.jwkSource.get(any(), any())).willReturn(Collections.singletonList(rsaJwk));
+
+		BiConsumer<JoseHeader.Builder, JwtClaimsSet.Builder> jwtCustomizer = mock(BiConsumer.class);
+		this.jwsEncoder.setJwtCustomizer(jwtCustomizer);
+
+		JoseHeader joseHeader = TestJoseHeaders.joseHeader().build();
+		JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet().build();
+
+		this.jwsEncoder.encode(joseHeader, jwtClaimsSet);
+
+		verify(jwtCustomizer).accept(any(JoseHeader.Builder.class), any(JwtClaimsSet.Builder.class));
+	}
+
+	@Test
+	public void encodeWhenJwkSelectFailedThenThrowJwtEncodingException() throws Exception {
+		given(this.jwkSource.get(any(), any())).willThrow(new KeySourceException("key source error"));
+
+		JoseHeader joseHeader = TestJoseHeaders.joseHeader().build();
+		JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet().build();
+
+		assertThatExceptionOfType(JwtEncodingException.class)
+				.isThrownBy(() -> this.jwsEncoder.encode(joseHeader, jwtClaimsSet))
+				.withMessageContaining("Failed to select a JWK signing key -> key source error");
+	}
+
+	@Test
+	public void encodeWhenJwkMultipleSelectedThenThrowJwtEncodingException() throws Exception {
+		RSAKey rsaJwk = TestJwks.DEFAULT_RSA_JWK;
+		given(this.jwkSource.get(any(), any())).willReturn(Arrays.asList(rsaJwk, rsaJwk));
+
+		JoseHeader joseHeader = TestJoseHeaders.joseHeader().build();
+		JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet().build();
+
+		assertThatExceptionOfType(JwtEncodingException.class)
+				.isThrownBy(() -> this.jwsEncoder.encode(joseHeader, jwtClaimsSet))
+				.withMessageContaining("Found multiple JWK signing keys for algorithm 'RS256'");
+	}
+
+	@Test
+	public void encodeWhenJwkSelectEmptyThenThrowJwtEncodingException() {
+		JoseHeader joseHeader = TestJoseHeaders.joseHeader().build();
+		JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet().build();
+
+		assertThatExceptionOfType(JwtEncodingException.class)
+				.isThrownBy(() -> this.jwsEncoder.encode(joseHeader, jwtClaimsSet))
+				.withMessageContaining("Failed to select a JWK signing key");
+	}
+
+	@Test
+	public void encodeWhenJwkKidNullThenThrowJwtEncodingException() throws Exception {
+		// @formatter:off
+		RSAKey rsaJwk = TestJwks.jwk(TestKeys.DEFAULT_PUBLIC_KEY, TestKeys.DEFAULT_PRIVATE_KEY)
+				.keyID(null)
+				.build();
+		// @formatter:on
+
+		given(this.jwkSource.get(any(), any())).willReturn(Collections.singletonList(rsaJwk));
+
+		JoseHeader joseHeader = TestJoseHeaders.joseHeader().build();
+		JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet().build();
+
+		assertThatExceptionOfType(JwtEncodingException.class)
+				.isThrownBy(() -> this.jwsEncoder.encode(joseHeader, jwtClaimsSet))
+				.withMessageContaining("The \"kid\" (key ID) from the selected JWK cannot be empty");
+	}
+
+	@Test
+	public void encodeWhenJwkUseEncryptionThenThrowJwtEncodingException() throws Exception {
+		// @formatter:off
+		RSAKey rsaJwk = TestJwks.jwk(TestKeys.DEFAULT_PUBLIC_KEY, TestKeys.DEFAULT_PRIVATE_KEY)
+				.keyUse(KeyUse.ENCRYPTION)
+				.build();
+		// @formatter:on
+
+		given(this.jwkSource.get(any(), any())).willReturn(Collections.singletonList(rsaJwk));
+
+		JoseHeader joseHeader = TestJoseHeaders.joseHeader().build();
+		JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet().build();
+
+		assertThatExceptionOfType(JwtEncodingException.class)
+				.isThrownBy(() -> this.jwsEncoder.encode(joseHeader, jwtClaimsSet)).withMessageContaining(
+				"Failed to create a JWS Signer -> The JWK use must be sig (signature) or unspecified");
+	}
+
+	@Test
+	public void encodeWhenSuccessThenDecodes() throws Exception {
+		RSAKey rsaJwk = TestJwks.DEFAULT_RSA_JWK;
+		given(this.jwkSource.get(any(), any())).willReturn(Collections.singletonList(rsaJwk));
+
+		JoseHeader joseHeader = TestJoseHeaders.joseHeader().build();
+		JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet().build();
+
+		Jwt encodedJws = this.jwsEncoder.encode(joseHeader, jwtClaimsSet);
+
+		// Assert headers/claims were added
+		assertThat(encodedJws.getHeaders().get(JoseHeaderNames.TYP)).isEqualTo("JWT");
+		assertThat(encodedJws.getHeaders().get(JoseHeaderNames.KID)).isEqualTo(rsaJwk.getKeyID());
+		assertThat(encodedJws.getId()).isNotNull();
+
+		NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withPublicKey(rsaJwk.toRSAPublicKey()).build();
+		jwtDecoder.decode(encodedJws.getTokenValue());
+	}
+
+	@Test
+	public void encodeWhenKeysRotatedThenNewKeyUsed() throws Exception {
+		TestJWKSource jwkSource = new TestJWKSource();
+		JWKSource<SecurityContext> jwkSourceDelegate = spy(new JWKSource<SecurityContext>() {
+			@Override
+			public List<JWK> get(JWKSelector jwkSelector, SecurityContext context) {
+				return jwkSource.get(jwkSelector, context);
+			}
+		});
+		NimbusJwsEncoder jwsEncoder = new NimbusJwsEncoder(jwkSourceDelegate);
+
+		JwkListResultCaptor jwkListResultCaptor = new JwkListResultCaptor();
+		willAnswer(jwkListResultCaptor).given(jwkSourceDelegate).get(any(), any());
+
+		JoseHeader joseHeader = TestJoseHeaders.joseHeader().build();
+		JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet().build();
+
+		Jwt encodedJws = jwsEncoder.encode(joseHeader, jwtClaimsSet);
+
+		JWK jwk1 = jwkListResultCaptor.getResult().get(0);
+		NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withPublicKey(((RSAKey) jwk1).toRSAPublicKey()).build();
+		jwtDecoder.decode(encodedJws.getTokenValue());
+
+		jwkSource.rotate(); // Trigger key rotation
+
+		encodedJws = jwsEncoder.encode(joseHeader, jwtClaimsSet);
+
+		JWK jwk2 = jwkListResultCaptor.getResult().get(0);
+		jwtDecoder = NimbusJwtDecoder.withPublicKey(((RSAKey) jwk2).toRSAPublicKey()).build();
+		jwtDecoder.decode(encodedJws.getTokenValue());
+
+		assertThat(jwk1.getKeyID()).isNotEqualTo(jwk2.getKeyID());
+	}
+
+	private static final class JwkListResultCaptor implements Answer<List<JWK>> {
+
+		private List<JWK> result;
+
+		private List<JWK> getResult() {
+			return this.result;
+		}
+
+		@SuppressWarnings("unchecked")
+		@Override
+		public List<JWK> answer(InvocationOnMock invocationOnMock) throws Throwable {
+			this.result = (List<JWK>) invocationOnMock.callRealMethod();
+			return this.result;
+		}
+
+	}
+
+	private static final class TestJWKSource implements JWKSource<SecurityContext> {
+
+		private int keyId = 1000;
+
+		private JWKSet jwkSet;
+
+		private TestJWKSource() {
+			init();
+		}
+
+		@Override
+		public List<JWK> get(JWKSelector jwkSelector, SecurityContext context) {
+			return jwkSelector.select(this.jwkSet);
+		}
+
+		private void init() {
+			// @formatter:off
+			RSAKey rsaJwk = TestJwks.jwk(TestKeys.DEFAULT_PUBLIC_KEY, TestKeys.DEFAULT_PRIVATE_KEY)
+					.keyID("rsa-jwk-" + this.keyId++)
+					.build();
+			ECKey ecJwk = TestJwks.jwk((ECPublicKey) TestKeys.DEFAULT_EC_KEY_PAIR.getPublic(), (ECPrivateKey) TestKeys.DEFAULT_EC_KEY_PAIR.getPrivate())
+					.keyID("ec-jwk-" + this.keyId++)
+					.build();
+			OctetSequenceKey secretJwk = TestJwks.jwk(TestKeys.DEFAULT_SECRET_KEY)
+					.keyID("secret-jwk-" + this.keyId++)
+					.build();
+			// @formatter:on
+			this.jwkSet = new JWKSet(Arrays.asList(rsaJwk, ecJwk, secretJwk));
+		}
+
+		private void rotate() {
+			init();
+		}
+
+	}
+
+}

+ 11 - 9
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/jose/TestJoseHeaders.java → oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/jwt/TestJoseHeaders.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 the original author or authors.
+ * Copyright 2020-2021 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.
@@ -13,38 +13,40 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.springframework.security.oauth2.jose;
-
-import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
+package org.springframework.security.oauth2.jwt;
 
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.UUID;
+
+import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
 
 /**
  * @author Joe Grandja
  */
-public class TestJoseHeaders {
+public final class TestJoseHeaders {
+
+	private TestJoseHeaders() {
+	}
 
 	public static JoseHeader.Builder joseHeader() {
 		return joseHeader(SignatureAlgorithm.RS256);
 	}
 
 	public static JoseHeader.Builder joseHeader(SignatureAlgorithm signatureAlgorithm) {
+		// @formatter:off
 		return JoseHeader.withAlgorithm(signatureAlgorithm)
 				.jwkSetUri("https://provider.com/oauth2/jwks")
 				.jwk(rsaJwk())
-				.keyId(UUID.randomUUID().toString())
+				.keyId("keyId")
 				.x509Uri("https://provider.com/oauth2/x509")
 				.x509CertificateChain(Arrays.asList("x509Cert1", "x509Cert2"))
 				.x509SHA1Thumbprint("x509SHA1Thumbprint")
 				.x509SHA256Thumbprint("x509SHA256Thumbprint")
-				.critical(Collections.singleton("custom-header-name"))
 				.type("JWT")
 				.contentType("jwt-content-type")
 				.header("custom-header-name", "custom-header-value");
+		// @formatter:on
 	}
 
 	private static Map<String, Object> rsaJwk() {

+ 8 - 4
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/jwt/TestJwtClaimsSets.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 the original author or authors.
+ * Copyright 2020-2021 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.
@@ -18,18 +18,21 @@ package org.springframework.security.oauth2.jwt;
 import java.time.Instant;
 import java.time.temporal.ChronoUnit;
 import java.util.Collections;
-import java.util.UUID;
 
 /**
  * @author Joe Grandja
  */
-public class TestJwtClaimsSets {
+public final class TestJwtClaimsSets {
+
+	private TestJwtClaimsSets() {
+	}
 
 	public static JwtClaimsSet.Builder jwtClaimsSet() {
 		String issuer = "https://provider.com";
 		Instant issuedAt = Instant.now();
 		Instant expiresAt = issuedAt.plus(1, ChronoUnit.HOURS);
 
+		// @formatter:off
 		return JwtClaimsSet.builder()
 				.issuer(issuer)
 				.subject("subject")
@@ -37,7 +40,8 @@ public class TestJwtClaimsSets {
 				.issuedAt(issuedAt)
 				.notBefore(issuedAt)
 				.expiresAt(expiresAt)
-				.id(UUID.randomUUID().toString())
+				.id("jti")
 				.claim("custom-claim-name", "custom-claim-value");
+		// @formatter:on
 	}
 }

+ 2 - 2
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProviderTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 the original author or authors.
+ * Copyright 2020-2021 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.
@@ -28,7 +28,7 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
 import org.springframework.security.oauth2.core.oidc.OidcIdToken;
 import org.springframework.security.oauth2.core.oidc.OidcScopes;
 import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
-import org.springframework.security.oauth2.jose.JoseHeaderNames;
+import org.springframework.security.oauth2.jwt.JoseHeaderNames;
 import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
 import org.springframework.security.oauth2.jwt.Jwt;
 import org.springframework.security.oauth2.jwt.JwtClaimsSet;

+ 2 - 2
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationProviderTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 the original author or authors.
+ * Copyright 2020-2021 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.
@@ -23,7 +23,7 @@ import org.springframework.security.oauth2.core.AuthorizationGrantType;
 import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
-import org.springframework.security.oauth2.jose.JoseHeaderNames;
+import org.springframework.security.oauth2.jwt.JoseHeaderNames;
 import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
 import org.springframework.security.oauth2.jwt.Jwt;
 import org.springframework.security.oauth2.jwt.JwtEncoder;

+ 2 - 2
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProviderTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 the original author or authors.
+ * Copyright 2020-2021 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,7 +25,7 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
 import org.springframework.security.oauth2.core.OAuth2RefreshToken;
 import org.springframework.security.oauth2.core.OAuth2RefreshToken2;
-import org.springframework.security.oauth2.jose.JoseHeaderNames;
+import org.springframework.security.oauth2.jwt.JoseHeaderNames;
 import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
 import org.springframework.security.oauth2.jwt.Jwt;
 import org.springframework.security.oauth2.jwt.JwtEncoder;

+ 43 - 49
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/JwkSetEndpointFilterTests.java → oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/NimbusJwkSetEndpointFilterTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 the original author or authors.
+ * Copyright 2020-2021 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.
@@ -15,62 +15,61 @@
  */
 package org.springframework.security.oauth2.server.authorization.web;
 
-import com.nimbusds.jose.JWSAlgorithm;
+import java.util.Arrays;
+import java.util.Collections;
+
+import javax.servlet.FilterChain;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
 import com.nimbusds.jose.jwk.ECKey;
 import com.nimbusds.jose.jwk.JWKSet;
 import com.nimbusds.jose.jwk.KeyUse;
+import com.nimbusds.jose.jwk.OctetSequenceKey;
 import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.jose.jwk.source.JWKSource;
+import com.nimbusds.jose.proc.SecurityContext;
 import org.junit.Before;
 import org.junit.Test;
+
 import org.springframework.http.MediaType;
 import org.springframework.mock.web.MockHttpServletRequest;
 import org.springframework.mock.web.MockHttpServletResponse;
-import org.springframework.security.crypto.key.AsymmetricKey;
-import org.springframework.security.crypto.key.CryptoKeySource;
-import org.springframework.security.crypto.key.SymmetricKey;
-import org.springframework.security.crypto.key.TestCryptoKeys;
-
-import javax.servlet.FilterChain;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
+import org.springframework.security.oauth2.jose.TestJwks;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.BDDMockito.given;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoInteractions;
-import static org.mockito.Mockito.when;
 
 /**
- * Tests for {@link JwkSetEndpointFilter}.
+ * Tests for {@link NimbusJwkSetEndpointFilter}.
  *
  * @author Joe Grandja
  */
-public class JwkSetEndpointFilterTests {
-	private CryptoKeySource keySource;
-	private JwkSetEndpointFilter filter;
+public class NimbusJwkSetEndpointFilterTests {
+	private JWKSource<SecurityContext> jwkSource;
+	private NimbusJwkSetEndpointFilter filter;
 
 	@Before
 	public void setUp() {
-		this.keySource = mock(CryptoKeySource.class);
-		this.filter = new JwkSetEndpointFilter(this.keySource);
+		this.jwkSource = mock(JWKSource.class);
+		this.filter = new NimbusJwkSetEndpointFilter(this.jwkSource);
 	}
 
 	@Test
-	public void constructorWhenKeySourceNullThenThrowIllegalArgumentException() {
-		assertThatThrownBy(() -> new JwkSetEndpointFilter(null))
+	public void constructorWhenJwkSourceNullThenThrowIllegalArgumentException() {
+		assertThatThrownBy(() -> new NimbusJwkSetEndpointFilter(null))
 				.isInstanceOf(IllegalArgumentException.class)
-				.hasMessage("keySource cannot be null");
+				.hasMessage("jwkSource cannot be null");
 	}
 
 	@Test
 	public void constructorWhenJwkSetEndpointUriNullThenThrowIllegalArgumentException() {
-		assertThatThrownBy(() -> new JwkSetEndpointFilter(this.keySource, null))
+		assertThatThrownBy(() -> new NimbusJwkSetEndpointFilter(this.jwkSource, null))
 				.isInstanceOf(IllegalArgumentException.class)
 				.hasMessage("jwkSetEndpointUri cannot be empty");
 	}
@@ -90,7 +89,7 @@ public class JwkSetEndpointFilterTests {
 
 	@Test
 	public void doFilterWhenJwkSetRequestPostThenNotProcessed() throws Exception {
-		String requestUri = JwkSetEndpointFilter.DEFAULT_JWK_SET_ENDPOINT_URI;
+		String requestUri = NimbusJwkSetEndpointFilter.DEFAULT_JWK_SET_ENDPOINT_URI;
 		MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri);
 		request.setServletPath(requestUri);
 		MockHttpServletResponse response = new MockHttpServletResponse();
@@ -103,12 +102,11 @@ public class JwkSetEndpointFilterTests {
 
 	@Test
 	public void doFilterWhenAsymmetricKeysThenJwkSetResponse() throws Exception {
-		AsymmetricKey rsaKey = TestCryptoKeys.rsaKey().build();
-		AsymmetricKey ecKey = TestCryptoKeys.ecKey().build();
-		when(this.keySource.getKeys()).thenReturn(
-				Stream.of(rsaKey, ecKey).collect(Collectors.toSet()));
+		RSAKey rsaJwk = TestJwks.DEFAULT_RSA_JWK;
+		ECKey ecJwk = TestJwks.DEFAULT_EC_JWK;
+		given(this.jwkSource.get(any(), any())).willReturn(Arrays.asList(rsaJwk, ecJwk));
 
-		String requestUri = JwkSetEndpointFilter.DEFAULT_JWK_SET_ENDPOINT_URI;
+		String requestUri = NimbusJwkSetEndpointFilter.DEFAULT_JWK_SET_ENDPOINT_URI;
 		MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
 		request.setServletPath(requestUri);
 		MockHttpServletResponse response = new MockHttpServletResponse();
@@ -123,29 +121,25 @@ public class JwkSetEndpointFilterTests {
 		JWKSet jwkSet = JWKSet.parse(response.getContentAsString());
 		assertThat(jwkSet.getKeys()).hasSize(2);
 
-		RSAKey rsaJwk = (RSAKey) jwkSet.getKeyByKeyId(rsaKey.getId());
-		assertThat(rsaJwk).isNotNull();
-		assertThat(rsaJwk.toRSAPublicKey()).isEqualTo(rsaKey.getPublicKey());
-		assertThat(rsaJwk.toRSAPrivateKey()).isNull();
-		assertThat(rsaJwk.getKeyUse()).isEqualTo(KeyUse.SIGNATURE);
-		assertThat(rsaJwk.getAlgorithm()).isEqualTo(JWSAlgorithm.RS256);
-
-		ECKey ecJwk = (ECKey) jwkSet.getKeyByKeyId(ecKey.getId());
-		assertThat(ecJwk).isNotNull();
-		assertThat(ecJwk.toECPublicKey()).isEqualTo(ecKey.getPublicKey());
-		assertThat(ecJwk.toECPublicKey()).isEqualTo(ecKey.getPublicKey());
-		assertThat(ecJwk.toECPrivateKey()).isNull();
-		assertThat(ecJwk.getKeyUse()).isEqualTo(KeyUse.SIGNATURE);
-		assertThat(ecJwk.getAlgorithm()).isEqualTo(JWSAlgorithm.ES256);
+		RSAKey rsaJwkResult = (RSAKey) jwkSet.getKeyByKeyId(rsaJwk.getKeyID());
+		assertThat(rsaJwkResult).isNotNull();
+		assertThat(rsaJwkResult.toRSAPublicKey()).isEqualTo(rsaJwk.toRSAPublicKey());
+		assertThat(rsaJwkResult.toRSAPrivateKey()).isNull();
+		assertThat(rsaJwkResult.getKeyUse()).isEqualTo(KeyUse.SIGNATURE);
+
+		ECKey ecJwkResult = (ECKey) jwkSet.getKeyByKeyId(ecJwk.getKeyID());
+		assertThat(ecJwkResult).isNotNull();
+		assertThat(ecJwkResult.toECPublicKey()).isEqualTo(ecJwk.toECPublicKey());
+		assertThat(ecJwkResult.toECPrivateKey()).isNull();
+		assertThat(ecJwkResult.getKeyUse()).isEqualTo(KeyUse.SIGNATURE);
 	}
 
 	@Test
 	public void doFilterWhenSymmetricKeysThenJwkSetResponseEmpty() throws Exception {
-		SymmetricKey secretKey = TestCryptoKeys.secretKey().build();
-		when(this.keySource.getKeys()).thenReturn(
-				new HashSet<>(Collections.singleton(secretKey)));
+		OctetSequenceKey secretJwk = TestJwks.DEFAULT_SECRET_JWK;
+		given(this.jwkSource.get(any(), any())).willReturn(Collections.singletonList(secretJwk));
 
-		String requestUri = JwkSetEndpointFilter.DEFAULT_JWK_SET_ENDPOINT_URI;
+		String requestUri = NimbusJwkSetEndpointFilter.DEFAULT_JWK_SET_ENDPOINT_URI;
 		MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
 		request.setServletPath(requestUri);
 		MockHttpServletResponse response = new MockHttpServletResponse();

+ 13 - 7
samples/boot/oauth2-integration/authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 the original author or authors.
+ * Copyright 2020-2021 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.
@@ -15,12 +15,18 @@
  */
 package sample.config;
 
+import java.util.UUID;
+
+import com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.jose.jwk.source.JWKSource;
+import com.nimbusds.jose.proc.SecurityContext;
+import sample.jose.Jwks;
+
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Import;
 import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
-import org.springframework.security.crypto.key.CryptoKeySource;
-import org.springframework.security.crypto.key.StaticKeyGeneratingCryptoKeySource;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
 import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
 import org.springframework.security.oauth2.core.oidc.OidcScopes;
@@ -29,8 +35,6 @@ import org.springframework.security.oauth2.server.authorization.client.Registere
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
 import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
 
-import java.util.UUID;
-
 /**
  * @author Joe Grandja
  * @since 0.0.1
@@ -61,8 +65,10 @@ public class AuthorizationServerConfig {
 	// @formatter:on
 
 	@Bean
-	public CryptoKeySource keySource() {
-		return new StaticKeyGeneratingCryptoKeySource();
+	public JWKSource<SecurityContext> jwkSource() {
+		RSAKey rsaKey = Jwks.generateRsa();
+		JWKSet jwkSet = new JWKSet(rsaKey);
+		return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
 	}
 
 	@Bean

+ 74 - 0
samples/boot/oauth2-integration/authorizationserver/src/main/java/sample/jose/Jwks.java

@@ -0,0 +1,74 @@
+/*
+ * Copyright 2020-2021 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 sample.jose;
+
+import java.security.KeyPair;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.util.UUID;
+
+import javax.crypto.SecretKey;
+
+import com.nimbusds.jose.jwk.Curve;
+import com.nimbusds.jose.jwk.ECKey;
+import com.nimbusds.jose.jwk.OctetSequenceKey;
+import com.nimbusds.jose.jwk.RSAKey;
+
+/**
+ * @author Joe Grandja
+ * @since 0.1.0
+ */
+public final class Jwks {
+
+	private Jwks() {
+	}
+
+	public static RSAKey generateRsa() {
+		KeyPair keyPair = KeyGeneratorUtils.generateRsaKey();
+		RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
+		RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
+		// @formatter:off
+		return new RSAKey.Builder(publicKey)
+				.privateKey(privateKey)
+				.keyID(UUID.randomUUID().toString())
+				.build();
+		// @formatter:on
+	}
+
+	public static ECKey generateEc() {
+		KeyPair keyPair = KeyGeneratorUtils.generateEcKey();
+		ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();
+		ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate();
+		Curve curve = Curve.forECParameterSpec(publicKey.getParams());
+		// @formatter:off
+		return new ECKey.Builder(curve, publicKey)
+				.privateKey(privateKey)
+				.keyID(UUID.randomUUID().toString())
+				.build();
+		// @formatter:on
+	}
+
+	public static OctetSequenceKey generateSecret() {
+		SecretKey secretKey = KeyGeneratorUtils.generateSecretKey();
+		// @formatter:off
+		return new OctetSequenceKey.Builder(secretKey)
+				.keyID(UUID.randomUUID().toString())
+				.build();
+		// @formatter:on
+	}
+}

+ 9 - 5
oauth2-authorization-server/src/main/java/org/springframework/security/crypto/key/KeyGeneratorUtils.java → samples/boot/oauth2-integration/authorizationserver/src/main/java/sample/jose/KeyGeneratorUtils.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 the original author or authors.
+ * Copyright 2020-2021 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.
@@ -13,10 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.springframework.security.crypto.key;
+package sample.jose;
 
-import javax.crypto.KeyGenerator;
-import javax.crypto.SecretKey;
 import java.math.BigInteger;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
@@ -25,12 +23,18 @@ import java.security.spec.ECParameterSpec;
 import java.security.spec.ECPoint;
 import java.security.spec.EllipticCurve;
 
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+
 /**
  * @author Joe Grandja
- * @since 0.0.1
+ * @since 0.1.0
  */
 final class KeyGeneratorUtils {
 
+	private KeyGeneratorUtils() {
+	}
+
 	static SecretKey generateSecretKey() {
 		SecretKey hmacKey;
 		try {