From e4d5f4a540b9cd5a4f2d342603f2b2bebb46e122 Mon Sep 17 00:00:00 2001 From: Justin Richer Date: Fri, 23 May 2014 16:16:06 -0400 Subject: [PATCH] added system wide cache for all symmetric validators, closes # 557 --- .../client/OIDCAuthenticationFilter.java | 68 ++++-------- .../service/impl/SymmetricCacheService.java | 102 ++++++++++++++++++ .../connect/ConnectOAuth2RequestFactory.java | 74 ++++--------- .../JwtBearerAuthenticationProvider.java | 56 ++-------- 4 files changed, 146 insertions(+), 154 deletions(-) create mode 100644 openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/SymmetricCacheService.java diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthenticationFilter.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthenticationFilter.java index 1d0f33478..78dc50e10 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthenticationFilter.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthenticationFilter.java @@ -16,19 +16,18 @@ ******************************************************************************/ package org.mitre.openid.connect.client; -import static org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod.*; +import static org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod.PRIVATE_KEY; +import static org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod.SECRET_BASIC; +import static org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod.SECRET_JWT; import java.io.IOException; import java.math.BigInteger; import java.net.URI; -import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; -import java.security.spec.InvalidKeySpecException; import java.text.ParseException; import java.util.Date; import java.util.Map; -import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -37,9 +36,8 @@ import javax.servlet.http.HttpSession; import org.apache.http.client.HttpClient; import org.apache.http.impl.client.SystemDefaultHttpClient; import org.mitre.jwt.signer.service.JwtSigningAndValidationService; -import org.mitre.jwt.signer.service.impl.DefaultJwtSigningAndValidationService; import org.mitre.jwt.signer.service.impl.JWKSetCacheService; -import org.mitre.oauth2.model.ClientDetailsEntity; +import org.mitre.jwt.signer.service.impl.SymmetricCacheService; import org.mitre.oauth2.model.RegisteredClient; import org.mitre.openid.connect.client.model.IssuerServiceResponse; import org.mitre.openid.connect.client.service.AuthRequestOptionsService; @@ -65,18 +63,13 @@ import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; import com.google.common.base.Strings; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSHeader; -import com.nimbusds.jose.jwk.JWK; -import com.nimbusds.jose.jwk.OctetSequenceKey; -import com.nimbusds.jose.jwk.Use; import com.nimbusds.jose.util.Base64; -import com.nimbusds.jose.util.Base64URL; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.ReadOnlyJWTClaimsSet; import com.nimbusds.jwt.SignedJWT; @@ -104,6 +97,9 @@ public class OIDCAuthenticationFilter extends AbstractAuthenticationProcessingFi @Autowired private JWKSetCacheService validationServices; + @Autowired(required=false) + private SymmetricCacheService symmetricCacheService; + @Autowired(required=false) private JwtSigningAndValidationService authenticationSignerService; @@ -131,6 +127,17 @@ public class OIDCAuthenticationFilter extends AbstractAuthenticationProcessingFi @Override public void afterPropertiesSet() { super.afterPropertiesSet(); + + // if our JOSE validators don't get wired in, drop defaults into place + + if (validationServices == null) { + validationServices = new JWKSetCacheService(); + } + + if (symmetricCacheService == null) { + symmetricCacheService = new SymmetricCacheService(); + } + } /* @@ -324,7 +331,7 @@ public class OIDCAuthenticationFilter extends AbstractAuthenticationProcessingFi || alg.equals(JWSAlgorithm.HS512))) { // generate one based on client secret - signer = getSymmetricValidtor(clientConfig.getClient()); + signer = symmetricCacheService.getSymmetricValidtor(clientConfig.getClient()); } else if (PRIVATE_KEY.equals(clientConfig.getTokenEndpointAuthMethod())) { @@ -744,41 +751,4 @@ public class OIDCAuthenticationFilter extends AbstractAuthenticationProcessingFi this.authOptions = authOptions; } - /** - * Create a symmetric signing and validation service for the given client - * - * @param client - * @return - */ - private JwtSigningAndValidationService getSymmetricValidtor(ClientDetailsEntity client) { - - // TODO: cache - - if (client == null) { - logger.error("Couldn't create symmetric validator for null client"); - return null; - } - - if (Strings.isNullOrEmpty(client.getClientSecret())) { - logger.error("Couldn't create symmetric validator for client " + client.getClientId() + " without a client secret"); - return null; - } - - try { - - JWK jwk = new OctetSequenceKey(Base64URL.encode(client.getClientSecret()), Use.SIGNATURE, null, client.getClientId(), null, null, null); - Map keys = ImmutableMap.of(client.getClientId(), jwk); - JwtSigningAndValidationService service = new DefaultJwtSigningAndValidationService(keys); - - return service; - - } catch (NoSuchAlgorithmException e) { - logger.error("Couldn't create symmetric validator for client " + client.getClientId(), e); - } catch (InvalidKeySpecException e) { - logger.error("Couldn't create symmetric validator for client " + client.getClientId(), e); - } - - return null; - - } } diff --git a/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/SymmetricCacheService.java b/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/SymmetricCacheService.java new file mode 100644 index 000000000..c4bc943b7 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/SymmetricCacheService.java @@ -0,0 +1,102 @@ +package org.mitre.jwt.signer.service.impl; + +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import org.mitre.jwt.signer.service.JwtSigningAndValidationService; +import org.mitre.oauth2.model.ClientDetailsEntity; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import com.google.common.base.Strings; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableMap; +import com.google.common.util.concurrent.UncheckedExecutionException; +import com.nimbusds.jose.jwk.JWK; +import com.nimbusds.jose.jwk.OctetSequenceKey; +import com.nimbusds.jose.jwk.Use; +import com.nimbusds.jose.util.Base64URL; + +/** + * Creates and caches symmetrical validators for clients based on client secrets. + * + * @author jricher + * + */ +@Service +public class SymmetricCacheService { + + private static Logger logger = LoggerFactory.getLogger(SymmetricCacheService.class); + + private LoadingCache validators; + + + public SymmetricCacheService() { + validators = CacheBuilder.newBuilder() + .expireAfterAccess(24, TimeUnit.HOURS) + .maximumSize(100) + .build(new SymmetricValidatorBuilder()); + } + + + /** + * Create a symmetric signing and validation service for the given client + * + * @param client + * @return + */ + public JwtSigningAndValidationService getSymmetricValidtor(ClientDetailsEntity client) { + + if (client == null) { + logger.error("Couldn't create symmetric validator for null client"); + return null; + } + + if (Strings.isNullOrEmpty(client.getClientSecret())) { + logger.error("Couldn't create symmetric validator for client " + client.getClientId() + " without a client secret"); + return null; + } + + try { + return validators.get(client.getClientSecret()); + } catch (UncheckedExecutionException ue) { + logger.error("Problem loading client validator", ue); + return null; + } catch (ExecutionException e) { + logger.error("Problem loading client validator", e); + return null; + } + + } + + public class SymmetricValidatorBuilder extends CacheLoader { + @Override + public JwtSigningAndValidationService load(String key) throws Exception { + try { + + String id = "SYMMETRIC-KEY"; + + JWK jwk = new OctetSequenceKey(Base64URL.encode(key), Use.SIGNATURE, null, id, null, null, null); + Map keys = ImmutableMap.of(id, jwk); + JwtSigningAndValidationService service = new DefaultJwtSigningAndValidationService(keys); + + return service; + + } catch (NoSuchAlgorithmException e) { + logger.error("Couldn't create symmetric validator for client", e); + } catch (InvalidKeySpecException e) { + logger.error("Couldn't create symmetric validator for client", e); + } + + throw new IllegalArgumentException("Couldn't create symmetric validator for client"); + } + + } + +} diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/ConnectOAuth2RequestFactory.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/ConnectOAuth2RequestFactory.java index dd023e814..0a738a511 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/ConnectOAuth2RequestFactory.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/ConnectOAuth2RequestFactory.java @@ -16,8 +16,6 @@ ******************************************************************************/ package org.mitre.openid.connect; -import java.security.NoSuchAlgorithmException; -import java.security.spec.InvalidKeySpecException; import java.text.ParseException; import java.util.Collections; import java.util.Map; @@ -26,8 +24,8 @@ import java.util.UUID; import org.mitre.jwt.encryption.service.JwtEncryptionAndDecryptionService; import org.mitre.jwt.signer.service.JwtSigningAndValidationService; -import org.mitre.jwt.signer.service.impl.DefaultJwtSigningAndValidationService; import org.mitre.jwt.signer.service.impl.JWKSetCacheService; +import org.mitre.jwt.signer.service.impl.SymmetricCacheService; import org.mitre.oauth2.model.ClientDetailsEntity; import org.mitre.oauth2.service.ClientDetailsEntityService; import org.mitre.oauth2.service.SystemScopeService; @@ -35,23 +33,18 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.oauth2.common.exceptions.InvalidClientException; +import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; import org.springframework.security.oauth2.common.util.OAuth2Utils; import org.springframework.security.oauth2.provider.AuthorizationRequest; import org.springframework.security.oauth2.provider.DefaultOAuth2RequestFactory; import org.springframework.stereotype.Component; -import com.google.common.base.Strings; -import com.google.common.collect.ImmutableMap; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.nimbusds.jose.Algorithm; import com.nimbusds.jose.JWEObject.State; import com.nimbusds.jose.JWSAlgorithm; -import com.nimbusds.jose.jwk.JWK; -import com.nimbusds.jose.jwk.OctetSequenceKey; -import com.nimbusds.jose.jwk.Use; -import com.nimbusds.jose.util.Base64URL; import com.nimbusds.jwt.EncryptedJWT; import com.nimbusds.jwt.JWT; import com.nimbusds.jwt.JWTParser; @@ -68,6 +61,9 @@ public class ConnectOAuth2RequestFactory extends DefaultOAuth2RequestFactory { @Autowired private JWKSetCacheService validators; + + @Autowired + private SymmetricCacheService symmetricCacheService; @Autowired private SystemScopeService systemScopes; @@ -126,15 +122,19 @@ public class ConnectOAuth2RequestFactory extends DefaultOAuth2RequestFactory { } if (request.getClientId() != null) { - ClientDetailsEntity client = clientDetailsService.loadClientByClientId(request.getClientId()); - - if ((request.getScope() == null || request.getScope().isEmpty())) { - Set clientScopes = client.getScope(); - request.setScope(clientScopes); - } - - if (request.getExtensions().get("max_age") == null && client.getDefaultMaxAge() != null) { - request.getExtensions().put("max_age", client.getDefaultMaxAge().toString()); + try { + ClientDetailsEntity client = clientDetailsService.loadClientByClientId(request.getClientId()); + + if ((request.getScope() == null || request.getScope().isEmpty())) { + Set clientScopes = client.getScope(); + request.setScope(clientScopes); + } + + if (request.getExtensions().get("max_age") == null && client.getDefaultMaxAge() != null) { + request.getExtensions().put("max_age", client.getDefaultMaxAge().toString()); + } + } catch (OAuth2Exception e) { + logger.error("Caught OAuth2 exception trying to test client scopes and max age:", e); } } @@ -210,7 +210,7 @@ public class ConnectOAuth2RequestFactory extends DefaultOAuth2RequestFactory { // it's HMAC, we need to make a validator based on the client secret - JwtSigningAndValidationService validator = getSymmetricValidtor(client); + JwtSigningAndValidationService validator = symmetricCacheService.getSymmetricValidtor(client); if (validator == null) { throw new InvalidClientException("Unable to create signature validator for client's secret: " + client.getClientSecret()); @@ -366,40 +366,4 @@ public class ConnectOAuth2RequestFactory extends DefaultOAuth2RequestFactory { } } - /** - * Create a symmetric signing and validation service for the given client - * - * @param client - * @return - */ - private JwtSigningAndValidationService getSymmetricValidtor(ClientDetailsEntity client) { - - if (client == null) { - logger.error("Couldn't create symmetric validator for null client"); - return null; - } - - if (Strings.isNullOrEmpty(client.getClientSecret())) { - logger.error("Couldn't create symmetric validator for client " + client.getClientId() + " without a client secret"); - return null; - } - - try { - - JWK jwk = new OctetSequenceKey(Base64URL.encode(client.getClientSecret()), Use.SIGNATURE, null, client.getClientId(), null, null, null); - Map keys = ImmutableMap.of(client.getClientId(), jwk); - JwtSigningAndValidationService service = new DefaultJwtSigningAndValidationService(keys); - - return service; - - } catch (NoSuchAlgorithmException e) { - logger.error("Couldn't create symmetric validator for client " + client.getClientId(), e); - } catch (InvalidKeySpecException e) { - logger.error("Couldn't create symmetric validator for client " + client.getClientId(), e); - } - - return null; - - } - } diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JwtBearerAuthenticationProvider.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JwtBearerAuthenticationProvider.java index 9c7c7e0af..f46b45b12 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JwtBearerAuthenticationProvider.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JwtBearerAuthenticationProvider.java @@ -19,15 +19,12 @@ */ package org.mitre.openid.connect.assertion; -import java.security.NoSuchAlgorithmException; -import java.security.spec.InvalidKeySpecException; import java.text.ParseException; import java.util.Date; -import java.util.Map; import org.mitre.jwt.signer.service.JwtSigningAndValidationService; -import org.mitre.jwt.signer.service.impl.DefaultJwtSigningAndValidationService; import org.mitre.jwt.signer.service.impl.JWKSetCacheService; +import org.mitre.jwt.signer.service.impl.SymmetricCacheService; import org.mitre.oauth2.model.ClientDetailsEntity; import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod; import org.mitre.oauth2.service.ClientDetailsEntityService; @@ -42,13 +39,7 @@ import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.oauth2.common.exceptions.InvalidClientException; -import com.google.common.base.Strings; -import com.google.common.collect.ImmutableMap; import com.nimbusds.jose.JWSAlgorithm; -import com.nimbusds.jose.jwk.JWK; -import com.nimbusds.jose.jwk.OctetSequenceKey; -import com.nimbusds.jose.jwk.Use; -import com.nimbusds.jose.util.Base64URL; import com.nimbusds.jwt.JWT; import com.nimbusds.jwt.ReadOnlyJWTClaimsSet; import com.nimbusds.jwt.SignedJWT; @@ -64,6 +55,10 @@ public class JwtBearerAuthenticationProvider implements AuthenticationProvider { // map of verifiers, load keys for clients @Autowired private JWKSetCacheService validators; + + // map of symmetric verifiers for client secrets + @Autowired + private SymmetricCacheService symmetricCacheService; // Allow for time sync issues by having a window of X seconds. private int timeSkewAllowance = 300; @@ -123,7 +118,7 @@ public class JwtBearerAuthenticationProvider implements AuthenticationProvider { // it's HMAC, we need to make a validator based on the client secret - JwtSigningAndValidationService validator = getSymmetricValidtor(client); + JwtSigningAndValidationService validator = symmetricCacheService.getSymmetricValidtor(client); if (validator == null) { throw new AuthenticationServiceException("Unable to create signature validator for client's secret: " + client.getClientSecret()); @@ -199,43 +194,4 @@ public class JwtBearerAuthenticationProvider implements AuthenticationProvider { return (JwtBearerAssertionAuthenticationToken.class.isAssignableFrom(authentication)); } - - /** - * Create a symmetric signing and validation service for the given client - * - * @param client - * @return - */ - private JwtSigningAndValidationService getSymmetricValidtor(ClientDetailsEntity client) { - - // TODO: cache - - if (client == null) { - logger.error("Couldn't create symmetric validator for null client"); - return null; - } - - if (Strings.isNullOrEmpty(client.getClientSecret())) { - logger.error("Couldn't create symmetric validator for client " + client.getClientId() + " without a client secret"); - return null; - } - - try { - - JWK jwk = new OctetSequenceKey(Base64URL.encode(client.getClientSecret()), Use.SIGNATURE, null, client.getClientId(), null, null, null); - Map keys = ImmutableMap.of(client.getClientId(), jwk); - JwtSigningAndValidationService service = new DefaultJwtSigningAndValidationService(keys); - - return service; - - } catch (NoSuchAlgorithmException e) { - logger.error("Couldn't create symmetric validator for client " + client.getClientId(), e); - } catch (InvalidKeySpecException e) { - logger.error("Couldn't create symmetric validator for client " + client.getClientId(), e); - } - - return null; - - } - }