Repository: cxf Updated Branches: refs/heads/3.0.x-fixes e51a21d58 -> 1176da8d2
[CXF-5607] Introducing an sso-oidc module Project: http://git-wip-us.apache.org/repos/asf/cxf/repo Commit: http://git-wip-us.apache.org/repos/asf/cxf/commit/1176da8d Tree: http://git-wip-us.apache.org/repos/asf/cxf/tree/1176da8d Diff: http://git-wip-us.apache.org/repos/asf/cxf/diff/1176da8d Branch: refs/heads/3.0.x-fixes Commit: 1176da8d2285120605aaede0673982f173c2b0aa Parents: e51a21d Author: Sergey Beryozkin <[email protected]> Authored: Fri Nov 7 16:53:22 2014 +0000 Committer: Sergey Beryozkin <[email protected]> Committed: Fri Nov 7 16:55:09 2014 +0000 ---------------------------------------------------------------------- rt/pom.xml | 1 + rt/rs/security/sso/oidc/pom.xml | 51 ++++++++ .../rs/security/oidc/common/UserProfile.java | 25 ++++ .../rs/security/oidc/rp/IdTokenValidator.java | 121 +++++++++++++++++++ .../cxf/rs/security/oidc/rp/OidcUtils.java | 64 ++++++++++ 5 files changed, 262 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cxf/blob/1176da8d/rt/pom.xml ---------------------------------------------------------------------- diff --git a/rt/pom.xml b/rt/pom.xml index bc15faa..09996bc 100644 --- a/rt/pom.xml +++ b/rt/pom.xml @@ -65,6 +65,7 @@ <module>rs/security/jose</module> <module>rs/security/oauth-parent</module> <module>rs/security/sso/saml</module> + <module>rs/security/sso/oidc</module> <module>rs/security/cors</module> <module>management</module> <module>management-web</module> http://git-wip-us.apache.org/repos/asf/cxf/blob/1176da8d/rt/rs/security/sso/oidc/pom.xml ---------------------------------------------------------------------- diff --git a/rt/rs/security/sso/oidc/pom.xml b/rt/rs/security/sso/oidc/pom.xml new file mode 100644 index 0000000..566caef --- /dev/null +++ b/rt/rs/security/sso/oidc/pom.xml @@ -0,0 +1,51 @@ +<?xml version="1.0"?> +<!-- + 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. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <artifactId>cxf-rt-rs-security-sso-oidc</artifactId> + <packaging>bundle</packaging> + <name>Apache CXF Runtime OpenId Connect</name> + <description>Apache CXF Runtime OpenId Connect</description> + <url>http://cxf.apache.org</url> + <parent> + <groupId>org.apache.cxf</groupId> + <artifactId>cxf-parent</artifactId> + <version>3.1.0-SNAPSHOT</version> + <relativePath>../../../../../parent/pom.xml</relativePath> + </parent> + <dependencies> + <dependency> + <groupId>org.apache.cxf</groupId> + <artifactId>cxf-rt-rs-security-oauth2</artifactId> + <version>${project.version}</version> + </dependency> + <!--test dependencies--> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.easymock</groupId> + <artifactId>easymock</artifactId> + <scope>test</scope> + </dependency> + </dependencies> +</project> http://git-wip-us.apache.org/repos/asf/cxf/blob/1176da8d/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/common/UserProfile.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/common/UserProfile.java b/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/common/UserProfile.java new file mode 100644 index 0000000..9f519cf --- /dev/null +++ b/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/common/UserProfile.java @@ -0,0 +1,25 @@ +/** + * 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.rs.security.oidc.common; + +import org.apache.cxf.jaxrs.provider.json.JsonMapObject; + +public class UserProfile extends JsonMapObject { + +} http://git-wip-us.apache.org/repos/asf/cxf/blob/1176da8d/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/rp/IdTokenValidator.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/rp/IdTokenValidator.java b/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/rp/IdTokenValidator.java new file mode 100644 index 0000000..eb16698 --- /dev/null +++ b/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/rp/IdTokenValidator.java @@ -0,0 +1,121 @@ +/** + * 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.rs.security.oidc.rp; + +import org.apache.cxf.jaxrs.client.WebClient; +import org.apache.cxf.rs.security.jose.jwe.JweDecryptionProvider; +import org.apache.cxf.rs.security.jose.jwk.JsonWebKey; +import org.apache.cxf.rs.security.jose.jwk.JsonWebKeys; +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.oauth2.common.ClientAccessToken; + +public class IdTokenValidator { + private JweDecryptionProvider jweDecryptor; + private JwsSignatureVerifier jwsVerifier; + private String issuerId; + private int issuedAtRange; + + private WebClient jwkSetClient; + + public JwtToken validateIdToken(ClientAccessToken at, String clientId) { + String idToken = at.getParameters().get("id_token"); + if (idToken == null) { + throw new SecurityException("ID Token is missing"); + } + // Decrypt the token if needed + if (jweDecryptor != null) { + idToken = new String(jweDecryptor.decrypt(idToken).getContentText()); + } + + // read id_token into JwtToken + JwsJwtCompactConsumer jwtConsumer = new JwsJwtCompactConsumer(idToken); + JwtToken jwt = jwtConsumer.getJwtToken(); + System.out.println("JWT claims" + jwtConsumer.getDecodedJsonToken().getClaimsJson()); + // validate token signature + JwsSignatureVerifier theJwsVerifier = loadJwkSignatureVerifier(jwt); + if (!jwtConsumer.verifySignatureWith(theJwsVerifier)) { + throw new SecurityException("ID Token signature verification failed"); + } + + // validate audience + if (!clientId.equals(jwt.getClaims().getAudience())) { + throw new SecurityException("Invalid audience"); + } + + // validate the provider + if (!issuerId.equals(jwt.getClaims().getIssuer())) { + throw new SecurityException("Invalid provider"); + } + long currentTimeInSecs = System.currentTimeMillis() / 1000; + long expiryTimeInSecs = jwt.getClaims().getExpiryTime(); + if (currentTimeInSecs > expiryTimeInSecs) { + throw new SecurityException("The token expired"); + } + long issuedAtInSecs = jwt.getClaims().getIssuedAt(); + if (issuedAtInSecs > currentTimeInSecs || issuedAtRange > 0 + && issuedAtInSecs < currentTimeInSecs - issuedAtRange) { + throw new SecurityException("Invalid issuedAt"); + } + // validate at_hash + OidcUtils.validateAccessTokenHash(at, jwt); + return jwt; + } + + private JwsSignatureVerifier loadJwkSignatureVerifier(JwtToken jwt) { + if (jwsVerifier != null) { + return jwsVerifier; + } + if (jwkSetClient == null) { + throw new SecurityException("Provider Jwk Set Client is not available"); + } + String keyId = jwt.getHeaders().getKeyId(); + if (keyId == null) { + throw new SecurityException("Provider JWK key id is null"); + } + JsonWebKeys keys = jwkSetClient.get(JsonWebKeys.class); + JsonWebKey key = keys.getKey(keyId); + if (key == null) { + throw new SecurityException("JWK key with the key id: \"" + keyId + "\" is not available"); + } + return JwsUtils.getSignatureVerifier(key); + } + + public void setJweDecryptor(JweDecryptionProvider jweDecryptor) { + this.jweDecryptor = jweDecryptor; + } + + public void setJweVerifier(JwsSignatureVerifier theJwsVerifier) { + this.jwsVerifier = theJwsVerifier; + } + + public void setIssuerId(String issuerId) { + this.issuerId = issuerId; + } + + public void setJwkSetClient(WebClient jwkSetClient) { + this.jwkSetClient = jwkSetClient; + } + + public void setIssuedAtRange(int issuedAtRange) { + this.issuedAtRange = issuedAtRange; + } +} http://git-wip-us.apache.org/repos/asf/cxf/blob/1176da8d/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/rp/OidcUtils.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/rp/OidcUtils.java b/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/rp/OidcUtils.java new file mode 100644 index 0000000..0a2e71d --- /dev/null +++ b/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/rp/OidcUtils.java @@ -0,0 +1,64 @@ +/** + * 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.rs.security.oidc.rp; + +import java.security.NoSuchAlgorithmException; + +import org.apache.cxf.common.util.Base64UrlUtility; +import org.apache.cxf.common.util.StringUtils; +import org.apache.cxf.common.util.crypto.MessageDigestUtils; +import org.apache.cxf.rs.security.jose.jwt.JwtToken; +import org.apache.cxf.rs.security.oauth2.common.ClientAccessToken; + +public final class OidcUtils { + private OidcUtils() { + + } + + public static void validateAccessTokenHash(ClientAccessToken at, JwtToken jwt) { + validateHash(at.getTokenKey(), + (String)jwt.getClaims().getClaim("at_hash"), + jwt.getHeaders().getAlgorithm()); + } + public static void validateCodeHash(String code, JwtToken jwt) { + validateHash(code, + (String)jwt.getClaims().getClaim("c_hash"), + jwt.getHeaders().getAlgorithm()); + } + private static void validateHash(String value, String theHash, String joseAlgo) { + String hash = calculateHash(value, joseAlgo); + if (!hash.equals(theHash)) { + throw new SecurityException("Invalid hash"); + } + } + public static String calculateHash(String value, String joseAlgo) { + //TODO: map from the JOSE alg to a signature alg, + // for example, RS256 -> SHA-256 + // and calculate the chunk size based on the algo key size + // for example SHA-256 -> 256/8 = 32 and 32/2 = 16 bytes + try { + byte[] atBytes = StringUtils.toBytesASCII(value); + byte[] digest = MessageDigestUtils.createDigest(atBytes, MessageDigestUtils.ALGO_SHA_256); + return Base64UrlUtility.encodeChunk(digest, 0, 16); + } catch (NoSuchAlgorithmException ex) { + throw new SecurityException(ex); + } + } + +}
