Repository: cxf Updated Branches: refs/heads/3.1.x-fixes 36dc41e1b -> 3832cdfc4
Add support for role extraction with JWT validation Project: http://git-wip-us.apache.org/repos/asf/cxf/repo Commit: http://git-wip-us.apache.org/repos/asf/cxf/commit/11ea395d Tree: http://git-wip-us.apache.org/repos/asf/cxf/tree/11ea395d Diff: http://git-wip-us.apache.org/repos/asf/cxf/diff/11ea395d Branch: refs/heads/3.1.x-fixes Commit: 11ea395d0fa226c040f4ca3c4c6114a90ce34e06 Parents: 36dc41e Author: Colm O hEigeartaigh <[email protected]> Authored: Mon Nov 16 11:34:52 2015 +0000 Committer: Colm O hEigeartaigh <[email protected]> Committed: Mon Nov 16 12:02:00 2015 +0000 ---------------------------------------------------------------------- .../validator/jwt/DefaultJWTRoleParser.java | 89 +++++++++ .../sts/token/validator/jwt/JWTRoleParser.java | 44 +++++ .../token/validator/jwt/JWTTokenValidator.java | 25 ++- .../token/validator/JWTTokenValidatorTest.java | 198 +++++++++++++++++++ 4 files changed, 349 insertions(+), 7 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cxf/blob/11ea395d/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/validator/jwt/DefaultJWTRoleParser.java ---------------------------------------------------------------------- diff --git a/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/validator/jwt/DefaultJWTRoleParser.java b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/validator/jwt/DefaultJWTRoleParser.java new file mode 100644 index 0000000..64d7cf1 --- /dev/null +++ b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/validator/jwt/DefaultJWTRoleParser.java @@ -0,0 +1,89 @@ +/** + * 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.Principal; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import javax.security.auth.Subject; + +import org.apache.cxf.common.security.SimpleGroup; +import org.apache.cxf.rs.security.jose.jwt.JwtToken; +import org.apache.cxf.sts.token.validator.DefaultSubjectRoleParser; + +/** + * A default implementation to extract roles from a JWT token + */ +public class DefaultJWTRoleParser extends DefaultSubjectRoleParser implements JWTRoleParser { + + private boolean useJaasSubject = true; + private String roleClaim; + + /** + * Return the set of User/Principal roles from the token. + * @param principal the Principal associated with the token + * @param subject the JAAS Subject associated with a successful validation of the token + * @param token The JWTToken + * @return the set of User/Principal roles from the token. + */ + public Set<Principal> parseRolesFromToken( + Principal principal, Subject subject, JwtToken token + ) { + if (subject != null && useJaasSubject) { + return super.parseRolesFromSubject(principal, subject); + } + + Set<Principal> roles = null; + if (roleClaim != null && token != null && token.getClaims().containsProperty(roleClaim)) { + roles = new HashSet<>(); + String role = token.getClaims().getStringProperty(roleClaim).trim(); + for (String r : role.split(",")) { + roles.add(new SimpleGroup(r)); + } + } else { + roles = Collections.emptySet(); + } + + return roles; + } + + public boolean isUseJaasSubject() { + return useJaasSubject; + } + + /** + * Whether to get roles from the JAAS Subject (if not null) returned from SAML Assertion + * Validation or not. The default is true. + * @param useJaasSubject whether to get roles from the JAAS Subject or not + */ + public void setUseJaasSubject(boolean useJaasSubject) { + this.useJaasSubject = useJaasSubject; + } + + public String getRoleClaim() { + return roleClaim; + } + + public void setRoleClaim(String roleClaim) { + this.roleClaim = roleClaim; + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/cxf/blob/11ea395d/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/validator/jwt/JWTRoleParser.java ---------------------------------------------------------------------- diff --git a/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/validator/jwt/JWTRoleParser.java b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/validator/jwt/JWTRoleParser.java new file mode 100644 index 0000000..c651e2a --- /dev/null +++ b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/token/validator/jwt/JWTRoleParser.java @@ -0,0 +1,44 @@ +/** + * 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.Principal; +import java.util.Set; + +import javax.security.auth.Subject; + +import org.apache.cxf.rs.security.jose.jwt.JwtToken; +import org.apache.cxf.sts.token.validator.SubjectRoleParser; + +/** + * This interface defines a way to extract roles from a JWT Token + */ +public interface JWTRoleParser extends SubjectRoleParser { + + /** + * Return the set of User/Principal roles from the token. + * @param principal the Principal associated with the token + * @param subject the JAAS Subject associated with a successful validation of the token + * @param token The JWTToken + * @return the set of User/Principal roles from the token. + */ + Set<Principal> parseRolesFromToken( + Principal principal, Subject subject, JwtToken token + ); +} http://git-wip-us.apache.org/repos/asf/cxf/blob/11ea395d/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 index 837c3c1..110a611 100644 --- 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 @@ -21,6 +21,7 @@ package org.apache.cxf.sts.token.validator.jwt; import java.security.KeyStore; import java.security.Principal; import java.util.Properties; +import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -51,6 +52,7 @@ public class JWTTokenValidator implements TokenValidator { private static final Logger LOG = LogUtils.getL7dLogger(JWTTokenValidator.class); private int clockOffset; private int ttl; + private JWTRoleParser roleParser; /** * Return true if this TokenValidator implementation is capable of validating the @@ -134,14 +136,8 @@ public class JWTTokenValidator implements TokenValidator { 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) { @@ -163,6 +159,13 @@ public class JWTTokenValidator implements TokenValidator { if (isVerifiedWithAPublicKey(jwt)) { Principal principal = new SimplePrincipal(jwt.getClaims().getSubject()); response.setPrincipal(principal); + + // Parse roles from the validated token + if (roleParser != null) { + Set<Principal> roles = + roleParser.parseRolesFromToken(principal, null, jwt); + response.setRoles(roles); + } } validateTarget.setState(STATE.VALID); @@ -204,4 +207,12 @@ public class JWTTokenValidator implements TokenValidator { public void setTtl(int ttl) { this.ttl = ttl; } + + public JWTRoleParser getRoleParser() { + return roleParser; + } + + public void setRoleParser(JWTRoleParser roleParser) { + this.roleParser = roleParser; + } } http://git-wip-us.apache.org/repos/asf/cxf/blob/11ea395d/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 index 3e2bc05..6882751 100644 --- 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 @@ -21,6 +21,7 @@ package org.apache.cxf.sts.token.validator; import java.io.IOException; import java.security.Principal; import java.util.Properties; +import java.util.Set; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; @@ -29,6 +30,7 @@ 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.rs.security.jose.jwt.JwtClaims; import org.apache.cxf.sts.STSConstants; import org.apache.cxf.sts.StaticSTSProperties; import org.apache.cxf.sts.cache.DefaultInMemoryTokenStore; @@ -41,7 +43,11 @@ 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.DefaultJWTClaimsProvider; +import org.apache.cxf.sts.token.provider.jwt.JWTClaimsProvider; +import org.apache.cxf.sts.token.provider.jwt.JWTClaimsProviderParameters; import org.apache.cxf.sts.token.provider.jwt.JWTTokenProvider; +import org.apache.cxf.sts.token.validator.jwt.DefaultJWTRoleParser; import org.apache.cxf.sts.token.validator.jwt.JWTTokenValidator; import org.apache.cxf.ws.security.tokenstore.TokenStore; import org.apache.wss4j.common.crypto.Crypto; @@ -136,6 +142,182 @@ public class JWTTokenValidatorTest extends org.junit.Assert { assertTrue(validatorResponse.getToken().getState() == STATE.INVALID); } + @org.junit.Test + public void testUnsignedToken() throws Exception { + // Create + TokenProvider jwtTokenProvider = new JWTTokenProvider(); + ((JWTTokenProvider)jwtTokenProvider).setSignToken(false); + + 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 == 2); + + // 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); + } + + @org.junit.Test + public void testInvalidConditionJWT() throws Exception { + // Create + TokenProvider jwtTokenProvider = new JWTTokenProvider(); + ((JWTTokenProvider)jwtTokenProvider).setSignToken(true); + + DefaultJWTClaimsProvider jwtClaimsProvider = new DefaultJWTClaimsProvider(); + jwtClaimsProvider.setLifetime(1L); + ((JWTTokenProvider)jwtTokenProvider).setJwtClaimsProvider(jwtClaimsProvider); + + 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); + + Thread.sleep(1500L); + + // 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); + } + + @org.junit.Test + public void testChangedSignature() throws Exception { + // Create + TokenProvider jwtTokenProvider = new JWTTokenProvider(); + ((JWTTokenProvider)jwtTokenProvider).setSignToken(true); + + DefaultJWTClaimsProvider jwtClaimsProvider = new DefaultJWTClaimsProvider(); + jwtClaimsProvider.setLifetime(1L); + ((JWTTokenProvider)jwtTokenProvider).setJwtClaimsProvider(jwtClaimsProvider); + + 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(); + // Change the signature + token += "blah"; + assertNotNull(token); + assertTrue(token.split("\\.").length == 3); + + Thread.sleep(1500L); + + // 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); + } + + @org.junit.Test + public void testJWTWithRoles() throws Exception { + // Create + TokenProvider jwtTokenProvider = new JWTTokenProvider(); + ((JWTTokenProvider)jwtTokenProvider).setSignToken(true); + + JWTClaimsProvider claimsProvider = new RoleJWTClaimsProvider("manager"); + ((JWTTokenProvider)jwtTokenProvider).setJwtClaimsProvider(claimsProvider); + + 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(); + // Set the role + DefaultJWTRoleParser roleParser = new DefaultJWTRoleParser(); + roleParser.setRoleClaim("role"); + ((JWTTokenValidator)jwtTokenValidator).setRoleParser(roleParser); + 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); + Set<Principal> roles = validatorResponse.getRoles(); + assertTrue(roles != null && !roles.isEmpty()); + assertTrue(roles.iterator().next().getName().equals("manager")); + } + private TokenProviderParameters createProviderParameters() throws WSSecurityException { TokenProviderParameters parameters = new TokenProviderParameters(); @@ -243,4 +425,20 @@ public class JWTTokenValidatorTest extends org.junit.Assert { } } } + + private static class RoleJWTClaimsProvider extends DefaultJWTClaimsProvider { + + private String role; + + public RoleJWTClaimsProvider(String role) { + this.role = role; + } + + @Override + public JwtClaims getJwtClaims(JWTClaimsProviderParameters jwtClaimsProviderParameters) { + JwtClaims claims = super.getJwtClaims(jwtClaimsProviderParameters); + claims.setProperty("role", role); + return claims; + } + } }
