You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by co...@apache.org on 2015/11/13 17:29:16 UTC
cxf git commit: Adding JWTValidator
Repository: cxf
Updated Branches:
refs/heads/master 67ac0ab27 -> c8905fd54
Adding JWTValidator
Project: http://git-wip-us.apache.org/repos/asf/cxf/repo
Commit: http://git-wip-us.apache.org/repos/asf/cxf/commit/c8905fd5
Tree: http://git-wip-us.apache.org/repos/asf/cxf/tree/c8905fd5
Diff: http://git-wip-us.apache.org/repos/asf/cxf/diff/c8905fd5
Branch: refs/heads/master
Commit: c8905fd544457546be73f8887e154bb72dee7c7e
Parents: 67ac0ab
Author: Colm O hEigeartaigh <co...@apache.org>
Authored: Fri Nov 13 16:28:07 2015 +0000
Committer: Colm O hEigeartaigh <co...@apache.org>
Committed: Fri Nov 13 16:28:07 2015 +0000
----------------------------------------------------------------------
.../apache/cxf/sts/request/ReceivedToken.java | 7 +-
.../token/validator/jwt/JWTTokenValidator.java | 207 ++++++++++++++++
.../token/validator/JWTTokenValidatorTest.java | 246 +++++++++++++++++++
3 files changed, 459 insertions(+), 1 deletion(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cxf/blob/c8905fd5/services/sts/sts-core/src/main/java/org/apache/cxf/sts/request/ReceivedToken.java
----------------------------------------------------------------------
diff --git a/services/sts/sts-core/src/main/java/org/apache/cxf/sts/request/ReceivedToken.java b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/request/ReceivedToken.java
index c2e1aee..252ec60 100644
--- a/services/sts/sts-core/src/main/java/org/apache/cxf/sts/request/ReceivedToken.java
+++ b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/request/ReceivedToken.java
@@ -33,7 +33,7 @@ import org.apache.cxf.ws.security.sts.provider.STSException;
/**
* This class contains values that have been extracted from a received Token. The Token can be a
- * JAXB UsernameTokenType/BinarySecurityTokenType or a DOM Element.
+ * JAXB UsernameTokenType/BinarySecurityTokenType, a DOM Element or a String.
*/
public class ReceivedToken {
@@ -74,6 +74,11 @@ public class ReceivedToken {
}
this.token = receivedToken;
isDOMElement = true;
+ } else if (receivedToken instanceof String) {
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Found ValidateTarget String");
+ }
+ this.token = receivedToken;
} else {
LOG.fine("Found ValidateTarget object of unknown type");
throw new STSException(
http://git-wip-us.apache.org/repos/asf/cxf/blob/c8905fd5/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/validator/jwt/JWTTokenValidator.java
----------------------------------------------------------------------
diff --git a/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/validator/jwt/JWTTokenValidator.java b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/validator/jwt/JWTTokenValidator.java
new file mode 100644
index 0000000..837c3c1
--- /dev/null
+++ b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/validator/jwt/JWTTokenValidator.java
@@ -0,0 +1,207 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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
+ *
+ * http://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.apache.cxf.sts.token.validator.jwt;
+
+import java.security.KeyStore;
+import java.security.Principal;
+import java.util.Properties;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.common.security.SimplePrincipal;
+import org.apache.cxf.rs.security.jose.common.JoseConstants;
+import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm;
+import org.apache.cxf.rs.security.jose.jws.JwsJwtCompactConsumer;
+import org.apache.cxf.rs.security.jose.jws.JwsSignatureVerifier;
+import org.apache.cxf.rs.security.jose.jws.JwsUtils;
+import org.apache.cxf.rs.security.jose.jwt.JwtToken;
+import org.apache.cxf.rs.security.jose.jwt.JwtUtils;
+import org.apache.cxf.sts.STSPropertiesMBean;
+import org.apache.cxf.sts.request.ReceivedToken;
+import org.apache.cxf.sts.request.ReceivedToken.STATE;
+import org.apache.cxf.sts.token.validator.TokenValidator;
+import org.apache.cxf.sts.token.validator.TokenValidatorParameters;
+import org.apache.cxf.sts.token.validator.TokenValidatorResponse;
+import org.apache.cxf.ws.security.sts.provider.STSException;
+import org.apache.wss4j.common.crypto.Crypto;
+import org.apache.wss4j.common.crypto.Merlin;
+
+/**
+ * Validate a SAML Assertion. It is valid if it was issued and signed by this STS.
+ */
+public class JWTTokenValidator implements TokenValidator {
+
+ private static final Logger LOG = LogUtils.getL7dLogger(JWTTokenValidator.class);
+ private int clockOffset;
+ private int ttl;
+
+ /**
+ * Return true if this TokenValidator implementation is capable of validating the
+ * ReceivedToken argument.
+ */
+ public boolean canHandleToken(ReceivedToken validateTarget) {
+ return canHandleToken(validateTarget, null);
+ }
+
+ /**
+ * Return true if this TokenValidator implementation is capable of validating the
+ * ReceivedToken argument. The realm is ignored in this Validator.
+ */
+ public boolean canHandleToken(ReceivedToken validateTarget, String realm) {
+ Object token = validateTarget.getToken();
+ if (token instanceof String) {
+ try {
+ JwsJwtCompactConsumer jwtConsumer = new JwsJwtCompactConsumer((String)token);
+ if (jwtConsumer.getJwtToken() != null) {
+ return true;
+ }
+ } catch (RuntimeException ex) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Validate a Token using the given TokenValidatorParameters.
+ */
+ public TokenValidatorResponse validateToken(TokenValidatorParameters tokenParameters) {
+ LOG.fine("Validating JWT Token");
+ STSPropertiesMBean stsProperties = tokenParameters.getStsProperties();
+
+ TokenValidatorResponse response = new TokenValidatorResponse();
+ ReceivedToken validateTarget = tokenParameters.getToken();
+ validateTarget.setState(STATE.INVALID);
+ response.setToken(validateTarget);
+
+ String token = (String)validateTarget.getToken();
+ if (token == null) {
+ return response;
+ }
+
+ if (token.split("\\.").length != 3) {
+ LOG.log(Level.WARNING, "JWT Token appears not to be signed. Validation has failed");
+ return response;
+ }
+
+ JwsJwtCompactConsumer jwtConsumer = new JwsJwtCompactConsumer(token);
+ JwtToken jwt = jwtConsumer.getJwtToken();
+
+ // Verify the signature
+ Properties verificationProperties = new Properties();
+
+ Crypto signatureCrypto = stsProperties.getSignatureCrypto();
+ String alias = stsProperties.getSignatureUsername();
+
+ if (alias != null) {
+ verificationProperties.put(JoseConstants.RSSEC_KEY_STORE_ALIAS, alias);
+ }
+
+ if (!(signatureCrypto instanceof Merlin)) {
+ throw new STSException("Can't get the keystore", STSException.REQUEST_FAILED);
+ }
+ KeyStore keystore = ((Merlin)signatureCrypto).getKeyStore();
+ verificationProperties.put(JoseConstants.RSSEC_KEY_STORE, keystore);
+
+ JwsSignatureVerifier signatureVerifier =
+ JwsUtils.loadSignatureVerifier(verificationProperties, jwt.getJwsHeaders());
+
+ if (!jwtConsumer.verifySignatureWith(signatureVerifier)) {
+ return response;
+ }
+
+ try {
+ validateToken(jwt);
+ } catch (RuntimeException ex) {
+ LOG.log(Level.WARNING, "JWT token validation failed", ex);
+ return response;
+ }
+
+ /*
+ // Parse roles from the validated token
+ if (samlRoleParser != null) {
+ Set<Principal> roles =
+ samlRoleParser.parseRolesFromAssertion(principal, null, assertion);
+ response.setRoles(roles);
+ }
+
+ // Get the realm of the SAML token
+ String tokenRealm = null;
+ if (samlRealmCodec != null) {
+ tokenRealm = samlRealmCodec.getRealmFromToken(assertion);
+ // verify the realm against the cached token
+ if (secToken != null) {
+ Map<String, Object> props = secToken.getProperties();
+ if (props != null) {
+ String cachedRealm = (String)props.get(STSConstants.TOKEN_REALM);
+ if (cachedRealm != null && !tokenRealm.equals(cachedRealm)) {
+ return response;
+ }
+ }
+ }
+ }
+ response.setTokenRealm(tokenRealm);
+ */
+
+ if (isVerifiedWithAPublicKey(jwt)) {
+ Principal principal = new SimplePrincipal(jwt.getClaims().getSubject());
+ response.setPrincipal(principal);
+ }
+
+ validateTarget.setState(STATE.VALID);
+ LOG.fine("JWT Token successfully validated");
+
+ return response;
+ }
+
+ private boolean isVerifiedWithAPublicKey(JwtToken jwt) {
+ String alg = (String)jwt.getJwsHeader(JoseConstants.HEADER_ALGORITHM);
+ SignatureAlgorithm sigAlg = SignatureAlgorithm.getAlgorithm(alg);
+ return SignatureAlgorithm.isPublicKeyAlgorithm(sigAlg);
+ }
+
+ protected void validateToken(JwtToken jwt) {
+ // If we have no issued time then we need to have an expiry
+ boolean expiredRequired = jwt.getClaims().getIssuedAt() == null;
+ JwtUtils.validateJwtExpiry(jwt.getClaims(), clockOffset, expiredRequired);
+
+ JwtUtils.validateJwtNotBefore(jwt.getClaims(), clockOffset, false);
+
+ // If we have no expiry then we must have an issued at
+ boolean issuedAtRequired = jwt.getClaims().getExpiryTime() == null;
+ JwtUtils.validateJwtIssuedAt(jwt.getClaims(), ttl, clockOffset, issuedAtRequired);
+ }
+
+ public int getClockOffset() {
+ return clockOffset;
+ }
+
+ public void setClockOffset(int clockOffset) {
+ this.clockOffset = clockOffset;
+ }
+
+ public int getTtl() {
+ return ttl;
+ }
+
+ public void setTtl(int ttl) {
+ this.ttl = ttl;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cxf/blob/c8905fd5/services/sts/sts-core/src/test/java/org/apache/cxf/sts/token/validator/JWTTokenValidatorTest.java
----------------------------------------------------------------------
diff --git a/services/sts/sts-core/src/test/java/org/apache/cxf/sts/token/validator/JWTTokenValidatorTest.java b/services/sts/sts-core/src/test/java/org/apache/cxf/sts/token/validator/JWTTokenValidatorTest.java
new file mode 100644
index 0000000..3e2bc05
--- /dev/null
+++ b/services/sts/sts-core/src/test/java/org/apache/cxf/sts/token/validator/JWTTokenValidatorTest.java
@@ -0,0 +1,246 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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
+ *
+ * http://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.apache.cxf.sts.token.validator;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.Properties;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.UnsupportedCallbackException;
+
+import org.apache.cxf.jaxws.context.WebServiceContextImpl;
+import org.apache.cxf.jaxws.context.WrappedMessageContext;
+import org.apache.cxf.message.MessageImpl;
+import org.apache.cxf.sts.STSConstants;
+import org.apache.cxf.sts.StaticSTSProperties;
+import org.apache.cxf.sts.cache.DefaultInMemoryTokenStore;
+import org.apache.cxf.sts.common.PasswordCallbackHandler;
+import org.apache.cxf.sts.request.KeyRequirements;
+import org.apache.cxf.sts.request.ReceivedToken;
+import org.apache.cxf.sts.request.ReceivedToken.STATE;
+import org.apache.cxf.sts.request.TokenRequirements;
+import org.apache.cxf.sts.service.EncryptionProperties;
+import org.apache.cxf.sts.token.provider.TokenProvider;
+import org.apache.cxf.sts.token.provider.TokenProviderParameters;
+import org.apache.cxf.sts.token.provider.TokenProviderResponse;
+import org.apache.cxf.sts.token.provider.jwt.JWTTokenProvider;
+import org.apache.cxf.sts.token.validator.jwt.JWTTokenValidator;
+import org.apache.cxf.ws.security.tokenstore.TokenStore;
+import org.apache.wss4j.common.crypto.Crypto;
+import org.apache.wss4j.common.crypto.CryptoFactory;
+import org.apache.wss4j.common.ext.WSPasswordCallback;
+import org.apache.wss4j.common.ext.WSSecurityException;
+import org.apache.wss4j.common.principal.CustomTokenPrincipal;
+
+/**
+ * Some unit tests for validating JWTTokens.
+ */
+public class JWTTokenValidatorTest extends org.junit.Assert {
+ private static TokenStore tokenStore = new DefaultInMemoryTokenStore();
+
+ @org.junit.Test
+ public void testCreateAndValidateSignedJWT() throws Exception {
+ // Create
+ TokenProvider jwtTokenProvider = new JWTTokenProvider();
+ ((JWTTokenProvider)jwtTokenProvider).setSignToken(true);
+
+ TokenProviderParameters providerParameters = createProviderParameters();
+
+ assertTrue(jwtTokenProvider.canHandleToken(JWTTokenProvider.JWT_TOKEN_TYPE));
+ TokenProviderResponse providerResponse = jwtTokenProvider.createToken(providerParameters);
+ assertTrue(providerResponse != null);
+ assertTrue(providerResponse.getToken() != null && providerResponse.getTokenId() != null);
+
+ String token = (String)providerResponse.getToken();
+ assertNotNull(token);
+ assertTrue(token.split("\\.").length == 3);
+
+ // Validate the token
+ TokenValidator jwtTokenValidator = new JWTTokenValidator();
+ TokenValidatorParameters validatorParameters = createValidatorParameters();
+ TokenRequirements tokenRequirements = validatorParameters.getTokenRequirements();
+
+ // Create a ValidateTarget consisting of a JWT Token
+ ReceivedToken validateTarget = new ReceivedToken(token);
+ tokenRequirements.setValidateTarget(validateTarget);
+ validatorParameters.setToken(validateTarget);
+
+ assertTrue(jwtTokenValidator.canHandleToken(validateTarget));
+
+ TokenValidatorResponse validatorResponse =
+ jwtTokenValidator.validateToken(validatorParameters);
+ assertTrue(validatorResponse != null);
+ assertTrue(validatorResponse.getToken() != null);
+ assertTrue(validatorResponse.getToken().getState() == STATE.VALID);
+
+ Principal principal = validatorResponse.getPrincipal();
+ assertTrue(principal != null && principal.getName() != null);
+ }
+
+ @org.junit.Test
+ public void testInvalidSignature() throws Exception {
+ // Create
+ TokenProvider jwtTokenProvider = new JWTTokenProvider();
+ ((JWTTokenProvider)jwtTokenProvider).setSignToken(true);
+
+ TokenProviderParameters providerParameters = createProviderParameters();
+ Crypto crypto = CryptoFactory.getInstance(getEveCryptoProperties());
+ CallbackHandler callbackHandler = new EveCallbackHandler();
+ providerParameters.getStsProperties().setSignatureCrypto(crypto);
+ providerParameters.getStsProperties().setCallbackHandler(callbackHandler);
+ providerParameters.getStsProperties().setSignatureUsername("eve");
+
+ assertTrue(jwtTokenProvider.canHandleToken(JWTTokenProvider.JWT_TOKEN_TYPE));
+ TokenProviderResponse providerResponse = jwtTokenProvider.createToken(providerParameters);
+ assertTrue(providerResponse != null);
+ assertTrue(providerResponse.getToken() != null && providerResponse.getTokenId() != null);
+
+ String token = (String)providerResponse.getToken();
+ assertNotNull(token);
+ assertTrue(token.split("\\.").length == 3);
+
+ // Validate the token
+ TokenValidator jwtTokenValidator = new JWTTokenValidator();
+ TokenValidatorParameters validatorParameters = createValidatorParameters();
+ TokenRequirements tokenRequirements = validatorParameters.getTokenRequirements();
+
+ // Create a ValidateTarget consisting of a JWT Token
+ ReceivedToken validateTarget = new ReceivedToken(token);
+ tokenRequirements.setValidateTarget(validateTarget);
+ validatorParameters.setToken(validateTarget);
+
+ assertTrue(jwtTokenValidator.canHandleToken(validateTarget));
+
+ TokenValidatorResponse validatorResponse =
+ jwtTokenValidator.validateToken(validatorParameters);
+ assertTrue(validatorResponse != null);
+ assertTrue(validatorResponse.getToken() != null);
+ assertTrue(validatorResponse.getToken().getState() == STATE.INVALID);
+ }
+
+ private TokenProviderParameters createProviderParameters() throws WSSecurityException {
+ TokenProviderParameters parameters = new TokenProviderParameters();
+
+ TokenRequirements tokenRequirements = new TokenRequirements();
+ tokenRequirements.setTokenType(JWTTokenProvider.JWT_TOKEN_TYPE);
+ parameters.setTokenRequirements(tokenRequirements);
+
+ KeyRequirements keyRequirements = new KeyRequirements();
+ parameters.setKeyRequirements(keyRequirements);
+
+ parameters.setTokenStore(tokenStore);
+
+ parameters.setPrincipal(new CustomTokenPrincipal("alice"));
+ // Mock up message context
+ MessageImpl msg = new MessageImpl();
+ WrappedMessageContext msgCtx = new WrappedMessageContext(msg);
+ WebServiceContextImpl webServiceContext = new WebServiceContextImpl(msgCtx);
+ parameters.setWebServiceContext(webServiceContext);
+
+ parameters.setAppliesToAddress("http://dummy-service.com/dummy");
+
+ // Add STSProperties object
+ StaticSTSProperties stsProperties = new StaticSTSProperties();
+ Crypto crypto = CryptoFactory.getInstance(getEncryptionProperties());
+ stsProperties.setSignatureCrypto(crypto);
+ stsProperties.setSignatureUsername("mystskey");
+ stsProperties.setCallbackHandler(new PasswordCallbackHandler());
+ stsProperties.setIssuer("STS");
+ parameters.setStsProperties(stsProperties);
+
+ parameters.setEncryptionProperties(new EncryptionProperties());
+ stsProperties.setEncryptionCrypto(crypto);
+ stsProperties.setEncryptionUsername("myservicekey");
+ stsProperties.setCallbackHandler(new PasswordCallbackHandler());
+
+ return parameters;
+ }
+
+ private TokenValidatorParameters createValidatorParameters() throws WSSecurityException {
+ TokenValidatorParameters parameters = new TokenValidatorParameters();
+
+ TokenRequirements tokenRequirements = new TokenRequirements();
+ tokenRequirements.setTokenType(STSConstants.STATUS);
+ parameters.setTokenRequirements(tokenRequirements);
+
+ KeyRequirements keyRequirements = new KeyRequirements();
+ parameters.setKeyRequirements(keyRequirements);
+
+ parameters.setPrincipal(new CustomTokenPrincipal("alice"));
+ // Mock up message context
+ MessageImpl msg = new MessageImpl();
+ WrappedMessageContext msgCtx = new WrappedMessageContext(msg);
+ WebServiceContextImpl webServiceContext = new WebServiceContextImpl(msgCtx);
+ parameters.setWebServiceContext(webServiceContext);
+
+ // Add STSProperties object
+ StaticSTSProperties stsProperties = new StaticSTSProperties();
+ Crypto crypto = CryptoFactory.getInstance(getEncryptionProperties());
+ stsProperties.setEncryptionCrypto(crypto);
+ stsProperties.setSignatureCrypto(crypto);
+ stsProperties.setEncryptionUsername("myservicekey");
+ stsProperties.setSignatureUsername("mystskey");
+ stsProperties.setCallbackHandler(new PasswordCallbackHandler());
+ stsProperties.setIssuer("STS");
+ parameters.setStsProperties(stsProperties);
+ parameters.setTokenStore(tokenStore);
+
+ return parameters;
+ }
+
+ private Properties getEncryptionProperties() {
+ Properties properties = new Properties();
+ properties.put(
+ "org.apache.wss4j.crypto.provider", "org.apache.wss4j.common.crypto.Merlin"
+ );
+ properties.put("org.apache.wss4j.crypto.merlin.keystore.password", "stsspass");
+ properties.put("org.apache.wss4j.crypto.merlin.keystore.file", "stsstore.jks");
+
+ return properties;
+ }
+
+ private Properties getEveCryptoProperties() {
+ Properties properties = new Properties();
+ properties.put(
+ "org.apache.wss4j.crypto.provider", "org.apache.wss4j.common.crypto.Merlin"
+ );
+ properties.put("org.apache.wss4j.crypto.merlin.keystore.password", "evespass");
+ properties.put("org.apache.wss4j.crypto.merlin.keystore.file", "eve.jks");
+
+ return properties;
+ }
+
+ public class EveCallbackHandler implements CallbackHandler {
+
+ public void handle(Callback[] callbacks) throws IOException,
+ UnsupportedCallbackException {
+ for (int i = 0; i < callbacks.length; i++) {
+ if (callbacks[i] instanceof WSPasswordCallback) { // CXF
+ WSPasswordCallback pc = (WSPasswordCallback) callbacks[i];
+ if ("eve".equals(pc.getIdentifier())) {
+ pc.setPassword("evekpass");
+ break;
+ }
+ }
+ }
+ }
+ }
+}