Replace ManagedKey with CryptoKey
Closes gh-105
This commit is contained in:
parent
8100568613
commit
a9423c6b13
@ -24,7 +24,7 @@ import org.springframework.security.authentication.AuthenticationManager;
|
|||||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
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.AbstractHttpConfigurer;
|
||||||
import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer;
|
||||||
import org.springframework.security.crypto.keys.KeyManager;
|
import org.springframework.security.crypto.key.CryptoKeySource;
|
||||||
import org.springframework.security.oauth2.jose.jws.NimbusJwsEncoder;
|
import org.springframework.security.oauth2.jose.jws.NimbusJwsEncoder;
|
||||||
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService;
|
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService;
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||||
@ -106,14 +106,14 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the key manager.
|
* Sets the source for cryptographic keys.
|
||||||
*
|
*
|
||||||
* @param keyManager the key manager
|
* @param keySource the source for cryptographic keys
|
||||||
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
|
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
|
||||||
*/
|
*/
|
||||||
public OAuth2AuthorizationServerConfigurer<B> keyManager(KeyManager keyManager) {
|
public OAuth2AuthorizationServerConfigurer<B> keySource(CryptoKeySource keySource) {
|
||||||
Assert.notNull(keyManager, "keyManager cannot be null");
|
Assert.notNull(keySource, "keySource cannot be null");
|
||||||
this.getBuilder().setSharedObject(KeyManager.class, keyManager);
|
this.getBuilder().setSharedObject(CryptoKeySource.class, keySource);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,7 +135,7 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
|
|||||||
getAuthorizationService(builder));
|
getAuthorizationService(builder));
|
||||||
builder.authenticationProvider(postProcess(clientAuthenticationProvider));
|
builder.authenticationProvider(postProcess(clientAuthenticationProvider));
|
||||||
|
|
||||||
NimbusJwsEncoder jwtEncoder = new NimbusJwsEncoder(getKeyManager(builder));
|
NimbusJwsEncoder jwtEncoder = new NimbusJwsEncoder(getKeySource(builder));
|
||||||
|
|
||||||
OAuth2AuthorizationCodeAuthenticationProvider authorizationCodeAuthenticationProvider =
|
OAuth2AuthorizationCodeAuthenticationProvider authorizationCodeAuthenticationProvider =
|
||||||
new OAuth2AuthorizationCodeAuthenticationProvider(
|
new OAuth2AuthorizationCodeAuthenticationProvider(
|
||||||
@ -172,7 +172,7 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configure(B builder) {
|
public void configure(B builder) {
|
||||||
JwkSetEndpointFilter jwkSetEndpointFilter = new JwkSetEndpointFilter(getKeyManager(builder));
|
JwkSetEndpointFilter jwkSetEndpointFilter = new JwkSetEndpointFilter(getKeySource(builder));
|
||||||
builder.addFilterBefore(postProcess(jwkSetEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
|
builder.addFilterBefore(postProcess(jwkSetEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
|
||||||
|
|
||||||
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
|
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
|
||||||
@ -237,16 +237,16 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
|
|||||||
return (!authorizationServiceMap.isEmpty() ? authorizationServiceMap.values().iterator().next() : null);
|
return (!authorizationServiceMap.isEmpty() ? authorizationServiceMap.values().iterator().next() : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <B extends HttpSecurityBuilder<B>> KeyManager getKeyManager(B builder) {
|
private static <B extends HttpSecurityBuilder<B>> CryptoKeySource getKeySource(B builder) {
|
||||||
KeyManager keyManager = builder.getSharedObject(KeyManager.class);
|
CryptoKeySource keySource = builder.getSharedObject(CryptoKeySource.class);
|
||||||
if (keyManager == null) {
|
if (keySource == null) {
|
||||||
keyManager = getKeyManagerBean(builder);
|
keySource = getKeySourceBean(builder);
|
||||||
builder.setSharedObject(KeyManager.class, keyManager);
|
builder.setSharedObject(CryptoKeySource.class, keySource);
|
||||||
}
|
}
|
||||||
return keyManager;
|
return keySource;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <B extends HttpSecurityBuilder<B>> KeyManager getKeyManagerBean(B builder) {
|
private static <B extends HttpSecurityBuilder<B>> CryptoKeySource getKeySourceBean(B builder) {
|
||||||
return builder.getSharedObject(ApplicationContext.class).getBean(KeyManager.class);
|
return builder.getSharedObject(ApplicationContext.class).getBean(CryptoKeySource.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* 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,0 +1,231 @@
|
|||||||
|
/*
|
||||||
|
* 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,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* 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();
|
||||||
|
|
||||||
|
}
|
@ -13,7 +13,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.springframework.security.crypto.keys;
|
package org.springframework.security.crypto.key;
|
||||||
|
|
||||||
import javax.crypto.KeyGenerator;
|
import javax.crypto.KeyGenerator;
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* 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,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,58 +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.keys;
|
|
||||||
|
|
||||||
import org.springframework.lang.Nullable;
|
|
||||||
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implementations of this interface are responsible for the management of {@link ManagedKey}(s),
|
|
||||||
* e.g. {@code javax.crypto.SecretKey}, {@code java.security.PrivateKey}, {@code java.security.PublicKey}, etc.
|
|
||||||
*
|
|
||||||
* @author Joe Grandja
|
|
||||||
* @since 0.0.1
|
|
||||||
* @see ManagedKey
|
|
||||||
*/
|
|
||||||
public interface KeyManager {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the {@link ManagedKey} identified by the provided {@code keyId},
|
|
||||||
* or {@code null} if not found.
|
|
||||||
*
|
|
||||||
* @param keyId the key ID
|
|
||||||
* @return the {@link ManagedKey}, or {@code null} if not found
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
ManagedKey findByKeyId(String keyId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a {@code Set} of {@link ManagedKey}(s) having the provided key {@code algorithm},
|
|
||||||
* or an empty {@code Set} if not found.
|
|
||||||
*
|
|
||||||
* @param algorithm the key algorithm
|
|
||||||
* @return a {@code Set} of {@link ManagedKey}(s), or an empty {@code Set} if not found
|
|
||||||
*/
|
|
||||||
Set<ManagedKey> findByAlgorithm(String algorithm);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a {@code Set} of the {@link ManagedKey}(s).
|
|
||||||
*
|
|
||||||
* @return a {@code Set} of the {@link ManagedKey}(s)
|
|
||||||
*/
|
|
||||||
Set<ManagedKey> getKeys();
|
|
||||||
|
|
||||||
}
|
|
@ -1,246 +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.keys;
|
|
||||||
|
|
||||||
import org.springframework.lang.Nullable;
|
|
||||||
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.time.Instant;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A {@code java.security.Key} that is managed by a {@link KeyManager}.
|
|
||||||
*
|
|
||||||
* @author Joe Grandja
|
|
||||||
* @since 0.0.1
|
|
||||||
* @see KeyManager
|
|
||||||
*/
|
|
||||||
public final class ManagedKey implements Serializable {
|
|
||||||
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
|
|
||||||
private Key key;
|
|
||||||
private PublicKey publicKey;
|
|
||||||
private String keyId;
|
|
||||||
private Instant activatedOn;
|
|
||||||
private Instant deactivatedOn;
|
|
||||||
|
|
||||||
private ManagedKey() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} if this is a symmetric key, {@code false} otherwise.
|
|
||||||
*
|
|
||||||
* @return {@code true} if this is a symmetric key, {@code false} otherwise
|
|
||||||
*/
|
|
||||||
public boolean isSymmetric() {
|
|
||||||
return SecretKey.class.isAssignableFrom(this.key.getClass());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} if this is a asymmetric key, {@code false} otherwise.
|
|
||||||
*
|
|
||||||
* @return {@code true} if this is a asymmetric key, {@code false} otherwise
|
|
||||||
*/
|
|
||||||
public boolean isAsymmetric() {
|
|
||||||
return PrivateKey.class.isAssignableFrom(this.key.getClass());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a type of {@code java.security.Key},
|
|
||||||
* e.g. {@code javax.crypto.SecretKey} or {@code java.security.PrivateKey}.
|
|
||||||
*
|
|
||||||
* @param <T> the type of {@code java.security.Key}
|
|
||||||
* @return the type of {@code java.security.Key}
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public <T extends Key> T getKey() {
|
|
||||||
return (T) this.key;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the {@code java.security.PublicKey} if this is a asymmetric key, {@code null} otherwise.
|
|
||||||
*
|
|
||||||
* @return the {@code java.security.PublicKey} if this is a asymmetric key, {@code null} otherwise
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public PublicKey getPublicKey() {
|
|
||||||
return this.publicKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the key ID.
|
|
||||||
*
|
|
||||||
* @return the key ID
|
|
||||||
*/
|
|
||||||
public String getKeyId() {
|
|
||||||
return this.keyId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the time when this key was activated.
|
|
||||||
*
|
|
||||||
* @return the time when this key was activated
|
|
||||||
*/
|
|
||||||
public Instant getActivatedOn() {
|
|
||||||
return this.activatedOn;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the time when this key was deactivated, {@code null} if still active.
|
|
||||||
*
|
|
||||||
* @return the time when this key was deactivated, {@code null} if still active
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public Instant getDeactivatedOn() {
|
|
||||||
return this.deactivatedOn;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} if this key is active, {@code false} otherwise.
|
|
||||||
*
|
|
||||||
* @return {@code true} if this key is active, {@code false} otherwise
|
|
||||||
*/
|
|
||||||
public boolean isActive() {
|
|
||||||
return getDeactivatedOn() == null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the key algorithm.
|
|
||||||
*
|
|
||||||
* @return the key algorithm
|
|
||||||
*/
|
|
||||||
public String getAlgorithm() {
|
|
||||||
return this.key.getAlgorithm();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null || getClass() != obj.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
ManagedKey that = (ManagedKey) obj;
|
|
||||||
return Objects.equals(this.keyId, that.keyId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(this.keyId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new {@link Builder}, initialized with the provided {@code javax.crypto.SecretKey}.
|
|
||||||
*
|
|
||||||
* @param secretKey the {@code javax.crypto.SecretKey}
|
|
||||||
* @return the {@link Builder}
|
|
||||||
*/
|
|
||||||
public static Builder withSymmetricKey(SecretKey secretKey) {
|
|
||||||
return new Builder(secretKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new {@link Builder}, initialized with the provided
|
|
||||||
* {@code java.security.PublicKey} and {@code java.security.PrivateKey}.
|
|
||||||
*
|
|
||||||
* @param publicKey the {@code java.security.PublicKey}
|
|
||||||
* @param privateKey the {@code java.security.PrivateKey}
|
|
||||||
* @return the {@link Builder}
|
|
||||||
*/
|
|
||||||
public static Builder withAsymmetricKey(PublicKey publicKey, PrivateKey privateKey) {
|
|
||||||
return new Builder(publicKey, privateKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A builder for {@link ManagedKey}.
|
|
||||||
*/
|
|
||||||
public static class Builder {
|
|
||||||
private Key key;
|
|
||||||
private PublicKey publicKey;
|
|
||||||
private String keyId;
|
|
||||||
private Instant activatedOn;
|
|
||||||
private Instant deactivatedOn;
|
|
||||||
|
|
||||||
private Builder(SecretKey secretKey) {
|
|
||||||
Assert.notNull(secretKey, "secretKey cannot be null");
|
|
||||||
this.key = secretKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Builder(PublicKey publicKey, PrivateKey privateKey) {
|
|
||||||
Assert.notNull(publicKey, "publicKey cannot be null");
|
|
||||||
Assert.notNull(privateKey, "privateKey cannot be null");
|
|
||||||
this.key = privateKey;
|
|
||||||
this.publicKey = publicKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the key ID.
|
|
||||||
*
|
|
||||||
* @param keyId the key ID
|
|
||||||
* @return the {@link Builder}
|
|
||||||
*/
|
|
||||||
public Builder keyId(String keyId) {
|
|
||||||
this.keyId = keyId;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the time when this key was activated.
|
|
||||||
*
|
|
||||||
* @param activatedOn the time when this key was activated
|
|
||||||
* @return the {@link Builder}
|
|
||||||
*/
|
|
||||||
public Builder activatedOn(Instant activatedOn) {
|
|
||||||
this.activatedOn = activatedOn;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the time when this key was deactivated.
|
|
||||||
*
|
|
||||||
* @param deactivatedOn the time when this key was deactivated
|
|
||||||
* @return the {@link Builder}
|
|
||||||
*/
|
|
||||||
public Builder deactivatedOn(Instant deactivatedOn) {
|
|
||||||
this.deactivatedOn = deactivatedOn;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds a new {@link ManagedKey}.
|
|
||||||
*
|
|
||||||
* @return a {@link ManagedKey}
|
|
||||||
*/
|
|
||||||
public ManagedKey build() {
|
|
||||||
Assert.hasText(this.keyId, "keyId cannot be empty");
|
|
||||||
Assert.notNull(this.activatedOn, "activatedOn cannot be null");
|
|
||||||
|
|
||||||
ManagedKey managedKey = new ManagedKey();
|
|
||||||
managedKey.key = this.key;
|
|
||||||
managedKey.publicKey = this.publicKey;
|
|
||||||
managedKey.keyId = this.keyId;
|
|
||||||
managedKey.activatedOn = this.activatedOn;
|
|
||||||
managedKey.deactivatedOn = this.deactivatedOn;
|
|
||||||
return managedKey;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,89 +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.keys;
|
|
||||||
|
|
||||||
import org.springframework.lang.Nullable;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
|
|
||||||
import javax.crypto.SecretKey;
|
|
||||||
import java.security.KeyPair;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import static org.springframework.security.crypto.keys.KeyGeneratorUtils.generateRsaKey;
|
|
||||||
import static org.springframework.security.crypto.keys.KeyGeneratorUtils.generateSecretKey;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An implementation of a {@link KeyManager} that generates the {@link ManagedKey}(s) when constructed.
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* <b>NOTE:</b> This implementation should ONLY be used during development/testing.
|
|
||||||
*
|
|
||||||
* @author Joe Grandja
|
|
||||||
* @since 0.0.1
|
|
||||||
* @see KeyManager
|
|
||||||
*/
|
|
||||||
public final class StaticKeyGeneratingKeyManager implements KeyManager {
|
|
||||||
private final Map<String, ManagedKey> keys;
|
|
||||||
|
|
||||||
public StaticKeyGeneratingKeyManager() {
|
|
||||||
this.keys = Collections.unmodifiableMap(new HashMap<>(generateKeys()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public ManagedKey findByKeyId(String keyId) {
|
|
||||||
Assert.hasText(keyId, "keyId cannot be empty");
|
|
||||||
return this.keys.get(keyId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<ManagedKey> findByAlgorithm(String algorithm) {
|
|
||||||
Assert.hasText(algorithm, "algorithm cannot be empty");
|
|
||||||
return this.keys.values().stream()
|
|
||||||
.filter(managedKey -> managedKey.getAlgorithm().equals(algorithm))
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<ManagedKey> getKeys() {
|
|
||||||
return new HashSet<>(this.keys.values());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Map<String, ManagedKey> generateKeys() {
|
|
||||||
KeyPair rsaKeyPair = generateRsaKey();
|
|
||||||
ManagedKey rsaManagedKey = ManagedKey.withAsymmetricKey(rsaKeyPair.getPublic(), rsaKeyPair.getPrivate())
|
|
||||||
.keyId(UUID.randomUUID().toString())
|
|
||||||
.activatedOn(Instant.now())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
SecretKey hmacKey = generateSecretKey();
|
|
||||||
ManagedKey secretManagedKey = ManagedKey.withSymmetricKey(hmacKey)
|
|
||||||
.keyId(UUID.randomUUID().toString())
|
|
||||||
.activatedOn(Instant.now())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
return Stream.of(rsaManagedKey, secretManagedKey)
|
|
||||||
.collect(Collectors.toMap(ManagedKey::getKeyId, v -> v));
|
|
||||||
}
|
|
||||||
}
|
|
@ -29,8 +29,9 @@ import com.nimbusds.jose.util.Base64URL;
|
|||||||
import com.nimbusds.jwt.JWTClaimsSet;
|
import com.nimbusds.jwt.JWTClaimsSet;
|
||||||
import com.nimbusds.jwt.SignedJWT;
|
import com.nimbusds.jwt.SignedJWT;
|
||||||
import org.springframework.core.convert.converter.Converter;
|
import org.springframework.core.convert.converter.Converter;
|
||||||
import org.springframework.security.crypto.keys.KeyManager;
|
import org.springframework.security.crypto.key.AsymmetricKey;
|
||||||
import org.springframework.security.crypto.keys.ManagedKey;
|
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.JoseHeader;
|
||||||
import org.springframework.security.oauth2.jose.JoseHeaderNames;
|
import org.springframework.security.oauth2.jose.JoseHeaderNames;
|
||||||
import org.springframework.security.oauth2.jwt.Jwt;
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
@ -58,7 +59,7 @@ import java.util.stream.Collectors;
|
|||||||
* An implementation of a {@link JwtEncoder} that encodes a JSON Web Token (JWT)
|
* An implementation of a {@link JwtEncoder} that encodes a JSON Web Token (JWT)
|
||||||
* using the JSON Web Signature (JWS) Compact Serialization format.
|
* using the JSON Web Signature (JWS) Compact Serialization format.
|
||||||
* The private/secret key used for signing the JWS is obtained
|
* The private/secret key used for signing the JWS is obtained
|
||||||
* from the {@link KeyManager} supplied via the constructor.
|
* from the {@link CryptoKeySource} supplied via the constructor.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* <b>NOTE:</b> This implementation uses the Nimbus JOSE + JWT SDK.
|
* <b>NOTE:</b> This implementation uses the Nimbus JOSE + JWT SDK.
|
||||||
@ -66,7 +67,7 @@ import java.util.stream.Collectors;
|
|||||||
* @author Joe Grandja
|
* @author Joe Grandja
|
||||||
* @since 0.0.1
|
* @since 0.0.1
|
||||||
* @see JwtEncoder
|
* @see JwtEncoder
|
||||||
* @see KeyManager
|
* @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/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">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://tools.ietf.org/html/rfc7515#section-3.1">JWS Compact Serialization</a>
|
||||||
@ -92,16 +93,16 @@ public final class NimbusJwsEncoder implements JwtEncoder {
|
|||||||
};
|
};
|
||||||
private static final Converter<JoseHeader, JWSHeader> jwsHeaderConverter = new JwsHeaderConverter();
|
private static final Converter<JoseHeader, JWSHeader> jwsHeaderConverter = new JwsHeaderConverter();
|
||||||
private static final Converter<JwtClaimsSet, JWTClaimsSet> jwtClaimsSetConverter = new JwtClaimsSetConverter();
|
private static final Converter<JwtClaimsSet, JWTClaimsSet> jwtClaimsSetConverter = new JwtClaimsSetConverter();
|
||||||
private final KeyManager keyManager;
|
private final CryptoKeySource keySource;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a {@code NimbusJwsEncoder} using the provided parameters.
|
* Constructs a {@code NimbusJwsEncoder} using the provided parameters.
|
||||||
*
|
*
|
||||||
* @param keyManager the key manager
|
* @param keySource the source for cryptographic keys
|
||||||
*/
|
*/
|
||||||
public NimbusJwsEncoder(KeyManager keyManager) {
|
public NimbusJwsEncoder(CryptoKeySource keySource) {
|
||||||
Assert.notNull(keyManager, "keyManager cannot be null");
|
Assert.notNull(keySource, "keySource cannot be null");
|
||||||
this.keyManager = keyManager;
|
this.keySource = keySource;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -109,24 +110,24 @@ public final class NimbusJwsEncoder implements JwtEncoder {
|
|||||||
Assert.notNull(headers, "headers cannot be null");
|
Assert.notNull(headers, "headers cannot be null");
|
||||||
Assert.notNull(claims, "claims cannot be null");
|
Assert.notNull(claims, "claims cannot be null");
|
||||||
|
|
||||||
ManagedKey managedKey = selectKey(headers);
|
CryptoKey<?> cryptoKey = selectKey(headers);
|
||||||
if (managedKey == null) {
|
if (cryptoKey == null) {
|
||||||
throw new JwtEncodingException(String.format(
|
throw new JwtEncodingException(String.format(
|
||||||
ENCODING_ERROR_MESSAGE_TEMPLATE,
|
ENCODING_ERROR_MESSAGE_TEMPLATE,
|
||||||
"Unsupported key for algorithm '" + headers.getJwsAlgorithm().getName() + "'"));
|
"Unsupported key for algorithm '" + headers.getJwsAlgorithm().getName() + "'"));
|
||||||
}
|
}
|
||||||
|
|
||||||
JWSSigner jwsSigner;
|
JWSSigner jwsSigner;
|
||||||
if (managedKey.isAsymmetric()) {
|
if (AsymmetricKey.class.isAssignableFrom(cryptoKey.getClass())) {
|
||||||
if (!managedKey.getAlgorithm().equals(RSA_KEY_TYPE)) {
|
if (!cryptoKey.getAlgorithm().equals(RSA_KEY_TYPE)) {
|
||||||
throw new JwtEncodingException(String.format(
|
throw new JwtEncodingException(String.format(
|
||||||
ENCODING_ERROR_MESSAGE_TEMPLATE,
|
ENCODING_ERROR_MESSAGE_TEMPLATE,
|
||||||
"Unsupported key type '" + managedKey.getAlgorithm() + "'"));
|
"Unsupported key type '" + cryptoKey.getAlgorithm() + "'"));
|
||||||
}
|
}
|
||||||
PrivateKey privateKey = managedKey.getKey();
|
PrivateKey privateKey = (PrivateKey) cryptoKey.getKey();
|
||||||
jwsSigner = new RSASSASigner(privateKey);
|
jwsSigner = new RSASSASigner(privateKey);
|
||||||
} else {
|
} else {
|
||||||
SecretKey secretKey = managedKey.getKey();
|
SecretKey secretKey = (SecretKey) cryptoKey.getKey();
|
||||||
try {
|
try {
|
||||||
jwsSigner = new MACSigner(secretKey);
|
jwsSigner = new MACSigner(secretKey);
|
||||||
} catch (KeyLengthException ex) {
|
} catch (KeyLengthException ex) {
|
||||||
@ -137,7 +138,7 @@ public final class NimbusJwsEncoder implements JwtEncoder {
|
|||||||
|
|
||||||
headers = JoseHeader.from(headers)
|
headers = JoseHeader.from(headers)
|
||||||
.type(JOSEObjectType.JWT.getType())
|
.type(JOSEObjectType.JWT.getType())
|
||||||
.keyId(managedKey.getKeyId())
|
.keyId(cryptoKey.getId())
|
||||||
.build();
|
.build();
|
||||||
JWSHeader jwsHeader = jwsHeaderConverter.convert(headers);
|
JWSHeader jwsHeader = jwsHeaderConverter.convert(headers);
|
||||||
|
|
||||||
@ -159,28 +160,19 @@ public final class NimbusJwsEncoder implements JwtEncoder {
|
|||||||
headers.getHeaders(), claims.getClaims());
|
headers.getHeaders(), claims.getClaims());
|
||||||
}
|
}
|
||||||
|
|
||||||
private ManagedKey selectKey(JoseHeader headers) {
|
private CryptoKey<?> selectKey(JoseHeader headers) {
|
||||||
JwsAlgorithm jwsAlgorithm = headers.getJwsAlgorithm();
|
JwsAlgorithm jwsAlgorithm = headers.getJwsAlgorithm();
|
||||||
String keyAlgorithm = jcaKeyAlgorithmMappings.get(jwsAlgorithm);
|
String keyAlgorithm = jcaKeyAlgorithmMappings.get(jwsAlgorithm);
|
||||||
if (!StringUtils.hasText(keyAlgorithm)) {
|
if (!StringUtils.hasText(keyAlgorithm)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<ManagedKey> matchingKeys = this.keyManager.findByAlgorithm(keyAlgorithm);
|
return this.keySource.getKeys().stream()
|
||||||
if (CollectionUtils.isEmpty(matchingKeys)) {
|
.filter(key -> key.getAlgorithm().equals(keyAlgorithm))
|
||||||
return null;
|
.findFirst()
|
||||||
}
|
|
||||||
|
|
||||||
return matchingKeys.stream()
|
|
||||||
.filter(ManagedKey::isActive)
|
|
||||||
.max(this::mostRecentActivated)
|
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int mostRecentActivated(ManagedKey managedKey1, ManagedKey managedKey2) {
|
|
||||||
return managedKey1.getActivatedOn().isAfter(managedKey2.getActivatedOn()) ? 1 : -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class JwsHeaderConverter implements Converter<JoseHeader, JWSHeader> {
|
private static class JwsHeaderConverter implements Converter<JoseHeader, JWSHeader> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -24,8 +24,8 @@ import com.nimbusds.jose.jwk.KeyUse;
|
|||||||
import com.nimbusds.jose.jwk.RSAKey;
|
import com.nimbusds.jose.jwk.RSAKey;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.security.crypto.keys.KeyManager;
|
import org.springframework.security.crypto.key.AsymmetricKey;
|
||||||
import org.springframework.security.crypto.keys.ManagedKey;
|
import org.springframework.security.crypto.key.CryptoKeySource;
|
||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
@ -47,7 +47,7 @@ import java.util.stream.Collectors;
|
|||||||
*
|
*
|
||||||
* @author Joe Grandja
|
* @author Joe Grandja
|
||||||
* @since 0.0.1
|
* @since 0.0.1
|
||||||
* @see KeyManager
|
* @see CryptoKeySource
|
||||||
* @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">JSON Web Key (JWK)</a>
|
||||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7517#section-5">Section 5 JWK Set Format</a>
|
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7517#section-5">Section 5 JWK Set Format</a>
|
||||||
*/
|
*/
|
||||||
@ -57,28 +57,28 @@ public class JwkSetEndpointFilter extends OncePerRequestFilter {
|
|||||||
*/
|
*/
|
||||||
public static final String DEFAULT_JWK_SET_ENDPOINT_URI = "/oauth2/jwks";
|
public static final String DEFAULT_JWK_SET_ENDPOINT_URI = "/oauth2/jwks";
|
||||||
|
|
||||||
private final KeyManager keyManager;
|
private final CryptoKeySource keySource;
|
||||||
private final RequestMatcher requestMatcher;
|
private final RequestMatcher requestMatcher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a {@code JwkSetEndpointFilter} using the provided parameters.
|
* Constructs a {@code JwkSetEndpointFilter} using the provided parameters.
|
||||||
*
|
*
|
||||||
* @param keyManager the key manager
|
* @param keySource the source for cryptographic keys
|
||||||
*/
|
*/
|
||||||
public JwkSetEndpointFilter(KeyManager keyManager) {
|
public JwkSetEndpointFilter(CryptoKeySource keySource) {
|
||||||
this(keyManager, DEFAULT_JWK_SET_ENDPOINT_URI);
|
this(keySource, DEFAULT_JWK_SET_ENDPOINT_URI);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a {@code JwkSetEndpointFilter} using the provided parameters.
|
* Constructs a {@code JwkSetEndpointFilter} using the provided parameters.
|
||||||
*
|
*
|
||||||
* @param keyManager the key manager
|
* @param keySource the source for cryptographic keys
|
||||||
* @param jwkSetEndpointUri the endpoint {@code URI} for JWK Set requests
|
* @param jwkSetEndpointUri the endpoint {@code URI} for JWK Set requests
|
||||||
*/
|
*/
|
||||||
public JwkSetEndpointFilter(KeyManager keyManager, String jwkSetEndpointUri) {
|
public JwkSetEndpointFilter(CryptoKeySource keySource, String jwkSetEndpointUri) {
|
||||||
Assert.notNull(keyManager, "keyManager cannot be null");
|
Assert.notNull(keySource, "keySource cannot be null");
|
||||||
Assert.hasText(jwkSetEndpointUri, "jwkSetEndpointUri cannot be empty");
|
Assert.hasText(jwkSetEndpointUri, "jwkSetEndpointUri cannot be empty");
|
||||||
this.keyManager = keyManager;
|
this.keySource = keySource;
|
||||||
this.requestMatcher = new AntPathRequestMatcher(jwkSetEndpointUri, HttpMethod.GET.name());
|
this.requestMatcher = new AntPathRequestMatcher(jwkSetEndpointUri, HttpMethod.GET.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,30 +101,31 @@ public class JwkSetEndpointFilter extends OncePerRequestFilter {
|
|||||||
|
|
||||||
private JWKSet buildJwkSet() {
|
private JWKSet buildJwkSet() {
|
||||||
return new JWKSet(
|
return new JWKSet(
|
||||||
this.keyManager.getKeys().stream()
|
this.keySource.getKeys().stream()
|
||||||
.filter(managedKey -> managedKey.isActive() && managedKey.isAsymmetric())
|
.filter(key -> AsymmetricKey.class.isAssignableFrom(key.getClass()))
|
||||||
|
.map(AsymmetricKey.class::cast)
|
||||||
.map(this::convert)
|
.map(this::convert)
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.collect(Collectors.toList())
|
.collect(Collectors.toList())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private JWK convert(ManagedKey managedKey) {
|
private JWK convert(AsymmetricKey asymmetricKey) {
|
||||||
JWK jwk = null;
|
JWK jwk = null;
|
||||||
if (managedKey.getPublicKey() instanceof RSAPublicKey) {
|
if (asymmetricKey.getPublicKey() instanceof RSAPublicKey) {
|
||||||
RSAPublicKey publicKey = (RSAPublicKey) managedKey.getPublicKey();
|
RSAPublicKey publicKey = (RSAPublicKey) asymmetricKey.getPublicKey();
|
||||||
jwk = new RSAKey.Builder(publicKey)
|
jwk = new RSAKey.Builder(publicKey)
|
||||||
.keyUse(KeyUse.SIGNATURE)
|
.keyUse(KeyUse.SIGNATURE)
|
||||||
.algorithm(JWSAlgorithm.RS256)
|
.algorithm(JWSAlgorithm.RS256)
|
||||||
.keyID(managedKey.getKeyId())
|
.keyID(asymmetricKey.getId())
|
||||||
.build();
|
.build();
|
||||||
} else if (managedKey.getPublicKey() instanceof ECPublicKey) {
|
} else if (asymmetricKey.getPublicKey() instanceof ECPublicKey) {
|
||||||
ECPublicKey publicKey = (ECPublicKey) managedKey.getPublicKey();
|
ECPublicKey publicKey = (ECPublicKey) asymmetricKey.getPublicKey();
|
||||||
Curve curve = Curve.forECParameterSpec(publicKey.getParams());
|
Curve curve = Curve.forECParameterSpec(publicKey.getParams());
|
||||||
jwk = new ECKey.Builder(curve, publicKey)
|
jwk = new ECKey.Builder(curve, publicKey)
|
||||||
.keyUse(KeyUse.SIGNATURE)
|
.keyUse(KeyUse.SIGNATURE)
|
||||||
.algorithm(JWSAlgorithm.ES256)
|
.algorithm(JWSAlgorithm.ES256)
|
||||||
.keyID(managedKey.getKeyId())
|
.keyID(asymmetricKey.getId())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
return jwk;
|
return jwk;
|
||||||
|
@ -27,8 +27,8 @@ import org.springframework.http.HttpHeaders;
|
|||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
||||||
import org.springframework.security.config.test.SpringTestRule;
|
import org.springframework.security.config.test.SpringTestRule;
|
||||||
import org.springframework.security.crypto.keys.KeyManager;
|
import org.springframework.security.crypto.key.CryptoKeySource;
|
||||||
import org.springframework.security.crypto.keys.StaticKeyGeneratingKeyManager;
|
import org.springframework.security.crypto.key.StaticKeyGeneratingCryptoKeySource;
|
||||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
@ -85,7 +85,7 @@ public class OAuth2AuthorizationCodeGrantTests {
|
|||||||
|
|
||||||
private static RegisteredClientRepository registeredClientRepository;
|
private static RegisteredClientRepository registeredClientRepository;
|
||||||
private static OAuth2AuthorizationService authorizationService;
|
private static OAuth2AuthorizationService authorizationService;
|
||||||
private static KeyManager keyManager;
|
private static CryptoKeySource keySource;
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public final SpringTestRule spring = new SpringTestRule();
|
public final SpringTestRule spring = new SpringTestRule();
|
||||||
@ -97,7 +97,7 @@ public class OAuth2AuthorizationCodeGrantTests {
|
|||||||
public static void init() {
|
public static void init() {
|
||||||
registeredClientRepository = mock(RegisteredClientRepository.class);
|
registeredClientRepository = mock(RegisteredClientRepository.class);
|
||||||
authorizationService = mock(OAuth2AuthorizationService.class);
|
authorizationService = mock(OAuth2AuthorizationService.class);
|
||||||
keyManager = new StaticKeyGeneratingKeyManager();
|
keySource = new StaticKeyGeneratingCryptoKeySource();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@ -266,8 +266,8 @@ public class OAuth2AuthorizationCodeGrantTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
KeyManager keyManager() {
|
CryptoKeySource keySource() {
|
||||||
return keyManager;
|
return keySource;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,8 +26,8 @@ import org.springframework.http.HttpHeaders;
|
|||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
||||||
import org.springframework.security.config.test.SpringTestRule;
|
import org.springframework.security.config.test.SpringTestRule;
|
||||||
import org.springframework.security.crypto.keys.KeyManager;
|
import org.springframework.security.crypto.key.CryptoKeySource;
|
||||||
import org.springframework.security.crypto.keys.StaticKeyGeneratingKeyManager;
|
import org.springframework.security.crypto.key.StaticKeyGeneratingCryptoKeySource;
|
||||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||||
@ -61,7 +61,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
|||||||
public class OAuth2ClientCredentialsGrantTests {
|
public class OAuth2ClientCredentialsGrantTests {
|
||||||
private static RegisteredClientRepository registeredClientRepository;
|
private static RegisteredClientRepository registeredClientRepository;
|
||||||
private static OAuth2AuthorizationService authorizationService;
|
private static OAuth2AuthorizationService authorizationService;
|
||||||
private static KeyManager keyManager;
|
private static CryptoKeySource keySource;
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public final SpringTestRule spring = new SpringTestRule();
|
public final SpringTestRule spring = new SpringTestRule();
|
||||||
@ -73,7 +73,7 @@ public class OAuth2ClientCredentialsGrantTests {
|
|||||||
public static void init() {
|
public static void init() {
|
||||||
registeredClientRepository = mock(RegisteredClientRepository.class);
|
registeredClientRepository = mock(RegisteredClientRepository.class);
|
||||||
authorizationService = mock(OAuth2AuthorizationService.class);
|
authorizationService = mock(OAuth2AuthorizationService.class);
|
||||||
keyManager = new StaticKeyGeneratingKeyManager();
|
keySource = new StaticKeyGeneratingCryptoKeySource();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@ -159,8 +159,8 @@ public class OAuth2ClientCredentialsGrantTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
KeyManager keyManager() {
|
CryptoKeySource keySource() {
|
||||||
return keyManager;
|
return keySource;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,8 +26,8 @@ import org.springframework.http.HttpHeaders;
|
|||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
||||||
import org.springframework.security.config.test.SpringTestRule;
|
import org.springframework.security.config.test.SpringTestRule;
|
||||||
import org.springframework.security.crypto.keys.KeyManager;
|
import org.springframework.security.crypto.key.CryptoKeySource;
|
||||||
import org.springframework.security.crypto.keys.StaticKeyGeneratingKeyManager;
|
import org.springframework.security.crypto.key.StaticKeyGeneratingCryptoKeySource;
|
||||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||||
@ -67,6 +67,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
|||||||
public class OAuth2RefreshTokenGrantTests {
|
public class OAuth2RefreshTokenGrantTests {
|
||||||
private static RegisteredClientRepository registeredClientRepository;
|
private static RegisteredClientRepository registeredClientRepository;
|
||||||
private static OAuth2AuthorizationService authorizationService;
|
private static OAuth2AuthorizationService authorizationService;
|
||||||
|
private static CryptoKeySource keySource;
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public final SpringTestRule spring = new SpringTestRule();
|
public final SpringTestRule spring = new SpringTestRule();
|
||||||
@ -78,6 +79,7 @@ public class OAuth2RefreshTokenGrantTests {
|
|||||||
public static void init() {
|
public static void init() {
|
||||||
registeredClientRepository = mock(RegisteredClientRepository.class);
|
registeredClientRepository = mock(RegisteredClientRepository.class);
|
||||||
authorizationService = mock(OAuth2AuthorizationService.class);
|
authorizationService = mock(OAuth2AuthorizationService.class);
|
||||||
|
keySource = new StaticKeyGeneratingCryptoKeySource();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@ -151,6 +153,8 @@ public class OAuth2RefreshTokenGrantTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
KeyManager keyManager() { return new StaticKeyGeneratingKeyManager(); }
|
CryptoKeySource keySource() {
|
||||||
|
return keySource;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,8 +27,8 @@ import org.springframework.http.HttpHeaders;
|
|||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
||||||
import org.springframework.security.config.test.SpringTestRule;
|
import org.springframework.security.config.test.SpringTestRule;
|
||||||
import org.springframework.security.crypto.keys.KeyManager;
|
import org.springframework.security.crypto.key.CryptoKeySource;
|
||||||
import org.springframework.security.crypto.keys.StaticKeyGeneratingKeyManager;
|
import org.springframework.security.crypto.key.StaticKeyGeneratingCryptoKeySource;
|
||||||
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
|
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
|
||||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||||
@ -66,7 +66,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
|||||||
public class OAuth2TokenRevocationTests {
|
public class OAuth2TokenRevocationTests {
|
||||||
private static RegisteredClientRepository registeredClientRepository;
|
private static RegisteredClientRepository registeredClientRepository;
|
||||||
private static OAuth2AuthorizationService authorizationService;
|
private static OAuth2AuthorizationService authorizationService;
|
||||||
private static KeyManager keyManager;
|
private static CryptoKeySource keySource;
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public final SpringTestRule spring = new SpringTestRule();
|
public final SpringTestRule spring = new SpringTestRule();
|
||||||
@ -78,7 +78,7 @@ public class OAuth2TokenRevocationTests {
|
|||||||
public static void init() {
|
public static void init() {
|
||||||
registeredClientRepository = mock(RegisteredClientRepository.class);
|
registeredClientRepository = mock(RegisteredClientRepository.class);
|
||||||
authorizationService = mock(OAuth2AuthorizationService.class);
|
authorizationService = mock(OAuth2AuthorizationService.class);
|
||||||
keyManager = new StaticKeyGeneratingKeyManager();
|
keySource = new StaticKeyGeneratingCryptoKeySource();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@ -181,8 +181,8 @@ public class OAuth2TokenRevocationTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
KeyManager keyManager() {
|
CryptoKeySource keySource() {
|
||||||
return keyManager;
|
return keySource;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,113 @@
|
|||||||
|
/*
|
||||||
|
* 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,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* 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());
|
||||||
|
}
|
||||||
|
}
|
@ -1,120 +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.keys;
|
|
||||||
|
|
||||||
import org.junit.BeforeClass;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import javax.crypto.SecretKey;
|
|
||||||
import java.security.Key;
|
|
||||||
import java.security.KeyPair;
|
|
||||||
import java.security.PrivateKey;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
|
||||||
import static org.springframework.security.crypto.keys.KeyGeneratorUtils.generateRsaKey;
|
|
||||||
import static org.springframework.security.crypto.keys.KeyGeneratorUtils.generateSecretKey;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests for {@link ManagedKey}.
|
|
||||||
*
|
|
||||||
* @author Joe Grandja
|
|
||||||
*/
|
|
||||||
public class ManagedKeyTests {
|
|
||||||
private static SecretKey secretKey;
|
|
||||||
private static KeyPair rsaKeyPair;
|
|
||||||
|
|
||||||
@BeforeClass
|
|
||||||
public static void init() {
|
|
||||||
secretKey = generateSecretKey();
|
|
||||||
rsaKeyPair = generateRsaKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void withSymmetricKeyWhenNullThenThrowIllegalArgumentException() {
|
|
||||||
assertThatThrownBy(() -> ManagedKey.withSymmetricKey(null))
|
|
||||||
.isInstanceOf(IllegalArgumentException.class)
|
|
||||||
.hasMessage("secretKey cannot be null");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void buildWhenKeyIdNullThenThrowIllegalArgumentException() {
|
|
||||||
assertThatThrownBy(() -> ManagedKey.withSymmetricKey(secretKey).build())
|
|
||||||
.isInstanceOf(IllegalArgumentException.class)
|
|
||||||
.hasMessage("keyId cannot be empty");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void buildWhenActivatedOnNullThenThrowIllegalArgumentException() {
|
|
||||||
assertThatThrownBy(() -> ManagedKey.withSymmetricKey(secretKey).keyId("keyId").build())
|
|
||||||
.isInstanceOf(IllegalArgumentException.class)
|
|
||||||
.hasMessage("activatedOn cannot be null");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void buildWhenSymmetricKeyAllAttributesProvidedThenAllAttributesAreSet() {
|
|
||||||
ManagedKey expectedManagedKey = TestManagedKeys.secretManagedKey().build();
|
|
||||||
|
|
||||||
ManagedKey managedKey = ManagedKey.withSymmetricKey(expectedManagedKey.getKey())
|
|
||||||
.keyId(expectedManagedKey.getKeyId())
|
|
||||||
.activatedOn(expectedManagedKey.getActivatedOn())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
assertThat(managedKey.isSymmetric()).isTrue();
|
|
||||||
assertThat(managedKey.<Key>getKey()).isInstanceOf(SecretKey.class);
|
|
||||||
assertThat(managedKey.<SecretKey>getKey()).isEqualTo(expectedManagedKey.getKey());
|
|
||||||
assertThat(managedKey.getPublicKey()).isNull();
|
|
||||||
assertThat(managedKey.getKeyId()).isEqualTo(expectedManagedKey.getKeyId());
|
|
||||||
assertThat(managedKey.getActivatedOn()).isEqualTo(expectedManagedKey.getActivatedOn());
|
|
||||||
assertThat(managedKey.getDeactivatedOn()).isEqualTo(expectedManagedKey.getDeactivatedOn());
|
|
||||||
assertThat(managedKey.isActive()).isTrue();
|
|
||||||
assertThat(managedKey.getAlgorithm()).isEqualTo(expectedManagedKey.getAlgorithm());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void withAsymmetricKeyWhenPublicKeyNullThenThrowIllegalArgumentException() {
|
|
||||||
assertThatThrownBy(() -> ManagedKey.withAsymmetricKey(null, rsaKeyPair.getPrivate()))
|
|
||||||
.isInstanceOf(IllegalArgumentException.class)
|
|
||||||
.hasMessage("publicKey cannot be null");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void withAsymmetricKeyWhenPrivateKeyNullThenThrowIllegalArgumentException() {
|
|
||||||
assertThatThrownBy(() -> ManagedKey.withAsymmetricKey(rsaKeyPair.getPublic(), null))
|
|
||||||
.isInstanceOf(IllegalArgumentException.class)
|
|
||||||
.hasMessage("privateKey cannot be null");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void buildWhenAsymmetricKeyAllAttributesProvidedThenAllAttributesAreSet() {
|
|
||||||
ManagedKey expectedManagedKey = TestManagedKeys.rsaManagedKey().build();
|
|
||||||
|
|
||||||
ManagedKey managedKey = ManagedKey.withAsymmetricKey(expectedManagedKey.getPublicKey(), expectedManagedKey.getKey())
|
|
||||||
.keyId(expectedManagedKey.getKeyId())
|
|
||||||
.activatedOn(expectedManagedKey.getActivatedOn())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
assertThat(managedKey.isAsymmetric()).isTrue();
|
|
||||||
assertThat(managedKey.<Key>getKey()).isInstanceOf(PrivateKey.class);
|
|
||||||
assertThat(managedKey.<PrivateKey>getKey()).isEqualTo(expectedManagedKey.getKey());
|
|
||||||
assertThat(managedKey.getPublicKey()).isNotNull();
|
|
||||||
assertThat(managedKey.getKeyId()).isEqualTo(expectedManagedKey.getKeyId());
|
|
||||||
assertThat(managedKey.getActivatedOn()).isEqualTo(expectedManagedKey.getActivatedOn());
|
|
||||||
assertThat(managedKey.getDeactivatedOn()).isEqualTo(expectedManagedKey.getDeactivatedOn());
|
|
||||||
assertThat(managedKey.isActive()).isTrue();
|
|
||||||
assertThat(managedKey.getAlgorithm()).isEqualTo(expectedManagedKey.getAlgorithm());
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,50 +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.keys;
|
|
||||||
|
|
||||||
import java.security.KeyPair;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import static org.springframework.security.crypto.keys.KeyGeneratorUtils.generateEcKey;
|
|
||||||
import static org.springframework.security.crypto.keys.KeyGeneratorUtils.generateRsaKey;
|
|
||||||
import static org.springframework.security.crypto.keys.KeyGeneratorUtils.generateSecretKey;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Joe Grandja
|
|
||||||
*/
|
|
||||||
public class TestManagedKeys {
|
|
||||||
|
|
||||||
public static ManagedKey.Builder secretManagedKey() {
|
|
||||||
return ManagedKey.withSymmetricKey(generateSecretKey())
|
|
||||||
.keyId(UUID.randomUUID().toString())
|
|
||||||
.activatedOn(Instant.now());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ManagedKey.Builder rsaManagedKey() {
|
|
||||||
KeyPair rsaKeyPair = generateRsaKey();
|
|
||||||
return ManagedKey.withAsymmetricKey(rsaKeyPair.getPublic(), rsaKeyPair.getPrivate())
|
|
||||||
.keyId(UUID.randomUUID().toString())
|
|
||||||
.activatedOn(Instant.now());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ManagedKey.Builder ecManagedKey() {
|
|
||||||
KeyPair ecKeyPair = generateEcKey();
|
|
||||||
return ManagedKey.withAsymmetricKey(ecKeyPair.getPublic(), ecKeyPair.getPrivate())
|
|
||||||
.keyId(UUID.randomUUID().toString())
|
|
||||||
.activatedOn(Instant.now());
|
|
||||||
}
|
|
||||||
}
|
|
@ -17,9 +17,9 @@ package org.springframework.security.oauth2.jose.jws;
|
|||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.springframework.security.crypto.keys.KeyManager;
|
import org.springframework.security.crypto.key.AsymmetricKey;
|
||||||
import org.springframework.security.crypto.keys.ManagedKey;
|
import org.springframework.security.crypto.key.CryptoKeySource;
|
||||||
import org.springframework.security.crypto.keys.TestManagedKeys;
|
import org.springframework.security.crypto.key.TestCryptoKeys;
|
||||||
import org.springframework.security.oauth2.jose.JoseHeader;
|
import org.springframework.security.oauth2.jose.JoseHeader;
|
||||||
import org.springframework.security.oauth2.jose.JoseHeaderNames;
|
import org.springframework.security.oauth2.jose.JoseHeaderNames;
|
||||||
import org.springframework.security.oauth2.jose.TestJoseHeaders;
|
import org.springframework.security.oauth2.jose.TestJoseHeaders;
|
||||||
@ -30,14 +30,12 @@ import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
|||||||
import org.springframework.security.oauth2.jwt.TestJwtClaimsSets;
|
import org.springframework.security.oauth2.jwt.TestJwtClaimsSets;
|
||||||
|
|
||||||
import java.security.interfaces.RSAPublicKey;
|
import java.security.interfaces.RSAPublicKey;
|
||||||
import java.time.Instant;
|
|
||||||
import java.time.temporal.ChronoUnit;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
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.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@ -47,20 +45,20 @@ import static org.mockito.Mockito.when;
|
|||||||
* @author Joe Grandja
|
* @author Joe Grandja
|
||||||
*/
|
*/
|
||||||
public class NimbusJwsEncoderTests {
|
public class NimbusJwsEncoderTests {
|
||||||
private KeyManager keyManager;
|
private CryptoKeySource keySource;
|
||||||
private NimbusJwsEncoder jwtEncoder;
|
private NimbusJwsEncoder jwtEncoder;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
this.keyManager = mock(KeyManager.class);
|
this.keySource = mock(CryptoKeySource.class);
|
||||||
this.jwtEncoder = new NimbusJwsEncoder(this.keyManager);
|
this.jwtEncoder = new NimbusJwsEncoder(this.keySource);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void constructorWhenKeyManagerNullThenThrowIllegalArgumentException() {
|
public void constructorWhenKeySourceNullThenThrowIllegalArgumentException() {
|
||||||
assertThatThrownBy(() -> new NimbusJwsEncoder(null))
|
assertThatThrownBy(() -> new NimbusJwsEncoder(null))
|
||||||
.isInstanceOf(IllegalArgumentException.class)
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
.hasMessage("keyManager cannot be null");
|
.hasMessage("keySource cannot be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -103,8 +101,8 @@ public class NimbusJwsEncoderTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void encodeWhenUnsupportedKeyTypeThenThrowJwtEncodingException() {
|
public void encodeWhenUnsupportedKeyTypeThenThrowJwtEncodingException() {
|
||||||
ManagedKey managedKey = TestManagedKeys.ecManagedKey().build();
|
AsymmetricKey ecKey = TestCryptoKeys.ecKey().build();
|
||||||
when(this.keyManager.findByAlgorithm(any())).thenReturn(Collections.singleton(managedKey));
|
when(this.keySource.getKeys()).thenReturn(Collections.singleton(ecKey));
|
||||||
|
|
||||||
JoseHeader joseHeader = TestJoseHeaders.joseHeader(SignatureAlgorithm.ES256).build();
|
JoseHeader joseHeader = TestJoseHeaders.joseHeader(SignatureAlgorithm.ES256).build();
|
||||||
JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet().build();
|
JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet().build();
|
||||||
@ -116,8 +114,8 @@ public class NimbusJwsEncoderTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void encodeWhenSuccessThenDecodes() {
|
public void encodeWhenSuccessThenDecodes() {
|
||||||
ManagedKey managedKey = TestManagedKeys.rsaManagedKey().build();
|
AsymmetricKey rsaKey = TestCryptoKeys.rsaKey().build();
|
||||||
when(this.keyManager.findByAlgorithm(any())).thenReturn(Collections.singleton(managedKey));
|
when(this.keySource.getKeys()).thenReturn(Collections.singleton(rsaKey));
|
||||||
|
|
||||||
JoseHeader joseHeader = TestJoseHeaders.joseHeader()
|
JoseHeader joseHeader = TestJoseHeaders.joseHeader()
|
||||||
.headers(headers -> headers.remove(JoseHeaderNames.CRIT))
|
.headers(headers -> headers.remove(JoseHeaderNames.CRIT))
|
||||||
@ -126,25 +124,17 @@ public class NimbusJwsEncoderTests {
|
|||||||
|
|
||||||
Jwt jws = this.jwtEncoder.encode(joseHeader, jwtClaimsSet);
|
Jwt jws = this.jwtEncoder.encode(joseHeader, jwtClaimsSet);
|
||||||
|
|
||||||
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withPublicKey((RSAPublicKey) managedKey.getPublicKey()).build();
|
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withPublicKey((RSAPublicKey) rsaKey.getPublicKey()).build();
|
||||||
jwtDecoder.decode(jws.getTokenValue());
|
jwtDecoder.decode(jws.getTokenValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void encodeWhenMultipleActiveKeysThenUseMostRecent() {
|
public void encodeWhenMultipleActiveKeysThenUseFirst() {
|
||||||
ManagedKey managedKeyActivated2DaysAgo = TestManagedKeys.rsaManagedKey()
|
AsymmetricKey rsaKey1 = TestCryptoKeys.rsaKey().build();
|
||||||
.activatedOn(Instant.now().minus(2, ChronoUnit.DAYS))
|
AsymmetricKey rsaKey2 = TestCryptoKeys.rsaKey().build();
|
||||||
.build();
|
when(this.keySource.getKeys()).thenReturn(
|
||||||
ManagedKey managedKeyActivated1DayAgo = TestManagedKeys.rsaManagedKey()
|
Stream.of(rsaKey1, rsaKey2)
|
||||||
.activatedOn(Instant.now().minus(1, ChronoUnit.DAYS))
|
.collect(Collectors.toCollection(LinkedHashSet::new)));
|
||||||
.build();
|
|
||||||
ManagedKey managedKeyActivatedToday = TestManagedKeys.rsaManagedKey()
|
|
||||||
.activatedOn(Instant.now())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
when(this.keyManager.findByAlgorithm(any())).thenReturn(
|
|
||||||
Stream.of(managedKeyActivated2DaysAgo, managedKeyActivated1DayAgo, managedKeyActivatedToday)
|
|
||||||
.collect(Collectors.toSet()));
|
|
||||||
|
|
||||||
JoseHeader joseHeader = TestJoseHeaders.joseHeader()
|
JoseHeader joseHeader = TestJoseHeaders.joseHeader()
|
||||||
.headers(headers -> headers.remove(JoseHeaderNames.CRIT))
|
.headers(headers -> headers.remove(JoseHeaderNames.CRIT))
|
||||||
@ -153,7 +143,7 @@ public class NimbusJwsEncoderTests {
|
|||||||
|
|
||||||
Jwt jws = this.jwtEncoder.encode(joseHeader, jwtClaimsSet);
|
Jwt jws = this.jwtEncoder.encode(joseHeader, jwtClaimsSet);
|
||||||
|
|
||||||
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withPublicKey((RSAPublicKey) managedKeyActivatedToday.getPublicKey()).build();
|
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withPublicKey((RSAPublicKey) rsaKey1.getPublicKey()).build();
|
||||||
jwtDecoder.decode(jws.getTokenValue());
|
jwtDecoder.decode(jws.getTokenValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,14 +25,14 @@ import org.junit.Test;
|
|||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
import org.springframework.mock.web.MockHttpServletResponse;
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
import org.springframework.security.crypto.keys.KeyManager;
|
import org.springframework.security.crypto.key.AsymmetricKey;
|
||||||
import org.springframework.security.crypto.keys.ManagedKey;
|
import org.springframework.security.crypto.key.CryptoKeySource;
|
||||||
import org.springframework.security.crypto.keys.TestManagedKeys;
|
import org.springframework.security.crypto.key.SymmetricKey;
|
||||||
|
import org.springframework.security.crypto.key.TestCryptoKeys;
|
||||||
|
|
||||||
import javax.servlet.FilterChain;
|
import javax.servlet.FilterChain;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@ -52,25 +52,25 @@ import static org.mockito.Mockito.when;
|
|||||||
* @author Joe Grandja
|
* @author Joe Grandja
|
||||||
*/
|
*/
|
||||||
public class JwkSetEndpointFilterTests {
|
public class JwkSetEndpointFilterTests {
|
||||||
private KeyManager keyManager;
|
private CryptoKeySource keySource;
|
||||||
private JwkSetEndpointFilter filter;
|
private JwkSetEndpointFilter filter;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
this.keyManager = mock(KeyManager.class);
|
this.keySource = mock(CryptoKeySource.class);
|
||||||
this.filter = new JwkSetEndpointFilter(this.keyManager);
|
this.filter = new JwkSetEndpointFilter(this.keySource);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void constructorWhenKeyManagerNullThenThrowIllegalArgumentException() {
|
public void constructorWhenKeySourceNullThenThrowIllegalArgumentException() {
|
||||||
assertThatThrownBy(() -> new JwkSetEndpointFilter(null))
|
assertThatThrownBy(() -> new JwkSetEndpointFilter(null))
|
||||||
.isInstanceOf(IllegalArgumentException.class)
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
.hasMessage("keyManager cannot be null");
|
.hasMessage("keySource cannot be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void constructorWhenJwkSetEndpointUriNullThenThrowIllegalArgumentException() {
|
public void constructorWhenJwkSetEndpointUriNullThenThrowIllegalArgumentException() {
|
||||||
assertThatThrownBy(() -> new JwkSetEndpointFilter(this.keyManager, null))
|
assertThatThrownBy(() -> new JwkSetEndpointFilter(this.keySource, null))
|
||||||
.isInstanceOf(IllegalArgumentException.class)
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
.hasMessage("jwkSetEndpointUri cannot be empty");
|
.hasMessage("jwkSetEndpointUri cannot be empty");
|
||||||
}
|
}
|
||||||
@ -103,10 +103,10 @@ public class JwkSetEndpointFilterTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void doFilterWhenAsymmetricKeysThenJwkSetResponse() throws Exception {
|
public void doFilterWhenAsymmetricKeysThenJwkSetResponse() throws Exception {
|
||||||
ManagedKey rsaManagedKey = TestManagedKeys.rsaManagedKey().build();
|
AsymmetricKey rsaKey = TestCryptoKeys.rsaKey().build();
|
||||||
ManagedKey ecManagedKey = TestManagedKeys.ecManagedKey().build();
|
AsymmetricKey ecKey = TestCryptoKeys.ecKey().build();
|
||||||
when(this.keyManager.getKeys()).thenReturn(
|
when(this.keySource.getKeys()).thenReturn(
|
||||||
Stream.of(rsaManagedKey, ecManagedKey).collect(Collectors.toSet()));
|
Stream.of(rsaKey, ecKey).collect(Collectors.toSet()));
|
||||||
|
|
||||||
String requestUri = JwkSetEndpointFilter.DEFAULT_JWK_SET_ENDPOINT_URI;
|
String requestUri = JwkSetEndpointFilter.DEFAULT_JWK_SET_ENDPOINT_URI;
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||||
@ -123,17 +123,17 @@ public class JwkSetEndpointFilterTests {
|
|||||||
JWKSet jwkSet = JWKSet.parse(response.getContentAsString());
|
JWKSet jwkSet = JWKSet.parse(response.getContentAsString());
|
||||||
assertThat(jwkSet.getKeys()).hasSize(2);
|
assertThat(jwkSet.getKeys()).hasSize(2);
|
||||||
|
|
||||||
RSAKey rsaJwk = (RSAKey) jwkSet.getKeyByKeyId(rsaManagedKey.getKeyId());
|
RSAKey rsaJwk = (RSAKey) jwkSet.getKeyByKeyId(rsaKey.getId());
|
||||||
assertThat(rsaJwk).isNotNull();
|
assertThat(rsaJwk).isNotNull();
|
||||||
assertThat(rsaJwk.toRSAPublicKey()).isEqualTo(rsaManagedKey.getPublicKey());
|
assertThat(rsaJwk.toRSAPublicKey()).isEqualTo(rsaKey.getPublicKey());
|
||||||
assertThat(rsaJwk.toRSAPrivateKey()).isNull();
|
assertThat(rsaJwk.toRSAPrivateKey()).isNull();
|
||||||
assertThat(rsaJwk.getKeyUse()).isEqualTo(KeyUse.SIGNATURE);
|
assertThat(rsaJwk.getKeyUse()).isEqualTo(KeyUse.SIGNATURE);
|
||||||
assertThat(rsaJwk.getAlgorithm()).isEqualTo(JWSAlgorithm.RS256);
|
assertThat(rsaJwk.getAlgorithm()).isEqualTo(JWSAlgorithm.RS256);
|
||||||
|
|
||||||
ECKey ecJwk = (ECKey) jwkSet.getKeyByKeyId(ecManagedKey.getKeyId());
|
ECKey ecJwk = (ECKey) jwkSet.getKeyByKeyId(ecKey.getId());
|
||||||
assertThat(ecJwk).isNotNull();
|
assertThat(ecJwk).isNotNull();
|
||||||
assertThat(ecJwk.toECPublicKey()).isEqualTo(ecManagedKey.getPublicKey());
|
assertThat(ecJwk.toECPublicKey()).isEqualTo(ecKey.getPublicKey());
|
||||||
assertThat(ecJwk.toECPublicKey()).isEqualTo(ecManagedKey.getPublicKey());
|
assertThat(ecJwk.toECPublicKey()).isEqualTo(ecKey.getPublicKey());
|
||||||
assertThat(ecJwk.toECPrivateKey()).isNull();
|
assertThat(ecJwk.toECPrivateKey()).isNull();
|
||||||
assertThat(ecJwk.getKeyUse()).isEqualTo(KeyUse.SIGNATURE);
|
assertThat(ecJwk.getKeyUse()).isEqualTo(KeyUse.SIGNATURE);
|
||||||
assertThat(ecJwk.getAlgorithm()).isEqualTo(JWSAlgorithm.ES256);
|
assertThat(ecJwk.getAlgorithm()).isEqualTo(JWSAlgorithm.ES256);
|
||||||
@ -141,32 +141,9 @@ public class JwkSetEndpointFilterTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void doFilterWhenSymmetricKeysThenJwkSetResponseEmpty() throws Exception {
|
public void doFilterWhenSymmetricKeysThenJwkSetResponseEmpty() throws Exception {
|
||||||
ManagedKey secretManagedKey = TestManagedKeys.secretManagedKey().build();
|
SymmetricKey secretKey = TestCryptoKeys.secretKey().build();
|
||||||
when(this.keyManager.getKeys()).thenReturn(
|
when(this.keySource.getKeys()).thenReturn(
|
||||||
new HashSet<>(Collections.singleton(secretManagedKey)));
|
new HashSet<>(Collections.singleton(secretKey)));
|
||||||
|
|
||||||
String requestUri = JwkSetEndpointFilter.DEFAULT_JWK_SET_ENDPOINT_URI;
|
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
|
||||||
request.setServletPath(requestUri);
|
|
||||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
|
||||||
FilterChain filterChain = mock(FilterChain.class);
|
|
||||||
|
|
||||||
this.filter.doFilter(request, response, filterChain);
|
|
||||||
|
|
||||||
verifyNoInteractions(filterChain);
|
|
||||||
|
|
||||||
assertThat(response.getContentType()).isEqualTo(MediaType.APPLICATION_JSON_VALUE);
|
|
||||||
|
|
||||||
JWKSet jwkSet = JWKSet.parse(response.getContentAsString());
|
|
||||||
assertThat(jwkSet.getKeys()).isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void doFilterWhenNoActiveKeysThenJwkSetResponseEmpty() throws Exception {
|
|
||||||
ManagedKey rsaManagedKey = TestManagedKeys.rsaManagedKey().deactivatedOn(Instant.now()).build();
|
|
||||||
ManagedKey ecManagedKey = TestManagedKeys.ecManagedKey().deactivatedOn(Instant.now()).build();
|
|
||||||
when(this.keyManager.getKeys()).thenReturn(
|
|
||||||
Stream.of(rsaManagedKey, ecManagedKey).collect(Collectors.toSet()));
|
|
||||||
|
|
||||||
String requestUri = JwkSetEndpointFilter.DEFAULT_JWK_SET_ENDPOINT_URI;
|
String requestUri = JwkSetEndpointFilter.DEFAULT_JWK_SET_ENDPOINT_URI;
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||||
|
@ -22,8 +22,8 @@ import org.springframework.security.config.annotation.web.configuration.OAuth2Au
|
|||||||
import org.springframework.security.core.userdetails.User;
|
import org.springframework.security.core.userdetails.User;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
import org.springframework.security.crypto.keys.KeyManager;
|
import org.springframework.security.crypto.key.CryptoKeySource;
|
||||||
import org.springframework.security.crypto.keys.StaticKeyGeneratingKeyManager;
|
import org.springframework.security.crypto.key.StaticKeyGeneratingCryptoKeySource;
|
||||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
|
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
|
||||||
@ -60,8 +60,8 @@ public class AuthorizationServerConfig {
|
|||||||
// @formatter:on
|
// @formatter:on
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public KeyManager keyManager() {
|
public CryptoKeySource keySource() {
|
||||||
return new StaticKeyGeneratingKeyManager();
|
return new StaticKeyGeneratingCryptoKeySource();
|
||||||
}
|
}
|
||||||
|
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
|
Loading…
Reference in New Issue
Block a user