Repository: cxf Updated Branches: refs/heads/master f675e3158 -> 4ff4d39b0
[CXF-5607] Adding some more OIDC utility code Project: http://git-wip-us.apache.org/repos/asf/cxf/repo Commit: http://git-wip-us.apache.org/repos/asf/cxf/commit/4ff4d39b Tree: http://git-wip-us.apache.org/repos/asf/cxf/tree/4ff4d39b Diff: http://git-wip-us.apache.org/repos/asf/cxf/diff/4ff4d39b Branch: refs/heads/master Commit: 4ff4d39b076281ac5ef193b72a77db9d5d7755f3 Parents: f675e31 Author: Sergey Beryozkin <[email protected]> Authored: Wed Nov 12 17:29:13 2014 +0000 Committer: Sergey Beryozkin <[email protected]> Committed: Wed Nov 12 17:29:13 2014 +0000 ---------------------------------------------------------------------- .../cxf/jaxrs/provider/json/JsonMapObject.java | 8 + .../cxf/rs/security/jose/jwt/JwtToken.java | 6 + .../oauth2/filters/OAuthRequestFilter.java | 26 ++- .../rs/security/oidc/common/UserAddress.java | 55 ++++++ .../rs/security/oidc/common/UserIdToken.java | 61 ++++++ .../rs/security/oidc/common/UserProfile.java | 71 ++++++- .../rs/security/oidc/rp/IdTokenValidator.java | 121 ------------ .../cxf/rs/security/oidc/rp/OidcUtils.java | 15 +- .../cxf/rs/security/oidc/rp/TokenValidator.java | 190 +++++++++++++++++++ 9 files changed, 423 insertions(+), 130 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cxf/blob/4ff4d39b/rt/rs/extensions/providers/src/main/java/org/apache/cxf/jaxrs/provider/json/JsonMapObject.java ---------------------------------------------------------------------- diff --git a/rt/rs/extensions/providers/src/main/java/org/apache/cxf/jaxrs/provider/json/JsonMapObject.java b/rt/rs/extensions/providers/src/main/java/org/apache/cxf/jaxrs/provider/json/JsonMapObject.java index 3001833..cfc98f2 100644 --- a/rt/rs/extensions/providers/src/main/java/org/apache/cxf/jaxrs/provider/json/JsonMapObject.java +++ b/rt/rs/extensions/providers/src/main/java/org/apache/cxf/jaxrs/provider/json/JsonMapObject.java @@ -69,6 +69,14 @@ public class JsonMapObject { return null; } } + public Boolean getBooleanProperty(String name) { + Object value = getProperty(name); + if (value != null) { + return value instanceof Boolean ? (Boolean)value : Boolean.parseBoolean(value.toString()); + } else { + return null; + } + } public int hashCode() { return values.hashCode(); http://git-wip-us.apache.org/repos/asf/cxf/blob/4ff4d39b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwt/JwtToken.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwt/JwtToken.java b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwt/JwtToken.java index e22e0ef..6a55854 100644 --- a/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwt/JwtToken.java +++ b/rt/rs/security/jose/src/main/java/org/apache/cxf/rs/security/jose/jwt/JwtToken.java @@ -35,6 +35,12 @@ public class JwtToken { public JwtClaims getClaims() { return claims; } + public Object getHeader(String name) { + return headers.getHeader(name); + } + public Object getClaim(String name) { + return claims.getClaim(name); + } public int hashCode() { return headers.hashCode() + 37 * claims.hashCode(); } http://git-wip-us.apache.org/repos/asf/cxf/blob/4ff4d39b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/filters/OAuthRequestFilter.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/filters/OAuthRequestFilter.java b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/filters/OAuthRequestFilter.java index 8f3f5d8..fb63639 100644 --- a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/filters/OAuthRequestFilter.java +++ b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/filters/OAuthRequestFilter.java @@ -20,6 +20,7 @@ package org.apache.cxf.rs.security.oauth2.filters; import java.security.Principal; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.logging.Logger; @@ -64,6 +65,8 @@ public class OAuthRequestFilter extends AbstractAccessTokenValidator private boolean useUserSubject; private boolean audienceIsEndpointAddress; private boolean checkFormData; + private List<String> requiredScopes = Collections.emptyList(); + private boolean allPermissionsMatch; public void filter(ContainerRequestContext context) { validateRequest(JAXRSUtils.getCurrentMessage()); @@ -93,12 +96,15 @@ public class OAuthRequestFilter extends AbstractAccessTokenValidator for (OAuthPermission perm : permissions) { boolean uriOK = checkRequestURI(req, perm.getUris()); boolean verbOK = checkHttpVerb(req, perm.getHttpVerbs()); - if (uriOK && verbOK) { + boolean scopeOk = checkScopeProperty(perm.getPermission()); + if (uriOK && verbOK && scopeOk) { matchingPermissions.add(perm); } } - if (permissions.size() > 0 && matchingPermissions.isEmpty()) { + if (permissions.size() > 0 && matchingPermissions.isEmpty() + || allPermissionsMatch && (matchingPermissions.size() != permissions.size()) + || !requiredScopes.isEmpty() && requiredScopes.size() != matchingPermissions.size()) { String message = "Client has no valid permissions"; LOG.warning(message); throw new WebApplicationException(403); @@ -150,7 +156,13 @@ public class OAuthRequestFilter extends AbstractAccessTokenValidator } return foundValidScope; } - + protected boolean checkScopeProperty(String scope) { + if (!requiredScopes.isEmpty()) { + return requiredScopes.contains(scope); + } else { + return true; + } + } public void setUseUserSubject(boolean useUserSubject) { this.useUserSubject = useUserSubject; } @@ -239,5 +251,13 @@ public class OAuthRequestFilter extends AbstractAccessTokenValidator AuthorizationUtils.throwAuthorizationFailure(supportedSchemes, realm); return null; } + + public void setRequiredScopes(List<String> requiredScopes) { + this.requiredScopes = requiredScopes; + } + + public void setAllPermissionsMatch(boolean allPermissionsMatch) { + this.allPermissionsMatch = allPermissionsMatch; + } } http://git-wip-us.apache.org/repos/asf/cxf/blob/4ff4d39b/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/common/UserAddress.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/common/UserAddress.java b/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/common/UserAddress.java new file mode 100644 index 0000000..46c4d96 --- /dev/null +++ b/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/common/UserAddress.java @@ -0,0 +1,55 @@ +/** + * 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 java.util.Map; + +import org.apache.cxf.jaxrs.provider.json.JsonMapObject; + +public class UserAddress extends JsonMapObject { + public static final String STREET = "street_address"; + public static final String LOCALITY = "locality"; + public static final String COUNTRY = "country"; + + public UserAddress() { + } + + public UserAddress(Map<String, Object> claims) { + super(claims); + } + + public void setStreet(String name) { + setProperty(STREET, name); + } + public String getStreet() { + return (String)getProperty(STREET); + } + public void setLocality(String name) { + setProperty(LOCALITY, name); + } + public String getLocality() { + return (String)getProperty(LOCALITY); + } + public void setCountry(String name) { + setProperty(COUNTRY, name); + } + public String getCountry() { + return (String)getProperty(COUNTRY); + } +} http://git-wip-us.apache.org/repos/asf/cxf/blob/4ff4d39b/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/common/UserIdToken.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/common/UserIdToken.java b/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/common/UserIdToken.java new file mode 100644 index 0000000..7db7991 --- /dev/null +++ b/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/common/UserIdToken.java @@ -0,0 +1,61 @@ +/** + * 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 java.util.Map; + +import org.apache.cxf.rs.security.jose.jwt.JwtClaims; + +public class UserIdToken extends JwtClaims { + public static final String AUTH_TIME_CLAIM = "auth_time"; + public static final String NONCE_CLAIM = "nonce"; + public static final String ACR_CLAIM = "acr"; + public static final String AZP_CLAIM = "azp"; + + public UserIdToken() { + } + + public UserIdToken(Map<String, Object> claims) { + super(claims); + } + public void setAuthenticationTime(Long time) { + setProperty(AUTH_TIME_CLAIM, time); + } + public Long getAuthenticationTime() { + return getLongProperty(AUTH_TIME_CLAIM); + } + public void setNonce(String nonce) { + setProperty(NONCE_CLAIM, nonce); + } + public String getNonce() { + return (String)getProperty(NONCE_CLAIM); + } + public void setAuthenticationContextRef(String ref) { + setProperty(ACR_CLAIM, ref); + } + public String getAuthenticationContextRef() { + return (String)getProperty(ACR_CLAIM); + } + public void setAuthorizedParty(String azp) { + setProperty(AZP_CLAIM, azp); + } + public String getAuthorizedParty() { + return (String)getProperty(AZP_CLAIM); + } +} http://git-wip-us.apache.org/repos/asf/cxf/blob/4ff4d39b/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 index 9f519cf..944c399 100644 --- 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 @@ -18,8 +18,75 @@ */ package org.apache.cxf.rs.security.oidc.common; -import org.apache.cxf.jaxrs.provider.json.JsonMapObject; +import java.util.Map; -public class UserProfile extends JsonMapObject { +import org.apache.cxf.helpers.CastUtils; +import org.apache.cxf.rs.security.jose.jwt.JwtClaims; +public class UserProfile extends JwtClaims { + public static final String NAME_CLAIM = "name"; + public static final String PROFILE_CLAIM = "profile"; + public static final String EMAIL_CLAIM = "email"; + public static final String EMAIL_VERIFIED_CLAIM = "email_verified"; + public static final String BIRTHDATE_CLAIM = "birthdate"; + public static final String PHONE_CLAIM = "phone_number"; + public static final String ADDRESS_CLAIM = "address"; + public UserProfile() { + } + + public UserProfile(Map<String, Object> claims) { + super(claims); + } + + public void setName(String name) { + setProperty(NAME_CLAIM, name); + } + public String getName() { + return (String)getProperty(NAME_CLAIM); + } + public void setProfile(String name) { + setProperty(PROFILE_CLAIM, name); + } + public String getProfile() { + return (String)getProperty(PROFILE_CLAIM); + } + public void setEmail(String name) { + setProperty(EMAIL_CLAIM, name); + } + public String getEmail() { + return (String)getProperty(EMAIL_CLAIM); + } + public void setEmailVerified(Boolean verified) { + setProperty(EMAIL_VERIFIED_CLAIM, verified); + } + public Boolean getEmailVerified() { + return getBooleanProperty(EMAIL_VERIFIED_CLAIM); + } + public void setBirthDate(String date) { + setProperty(BIRTHDATE_CLAIM, date); + } + public String getBirthdate() { + return (String)getProperty(BIRTHDATE_CLAIM); + } + public String getPhoneNumber() { + return (String)getProperty(PHONE_CLAIM); + } + public void setPhoneNumber(String name) { + setProperty(PHONE_CLAIM, name); + } + public UserAddress getUserAddress() { + Object value = getProperty(ADDRESS_CLAIM); + if (value instanceof UserAddress) { + return (UserAddress)value; + } else if (value instanceof Map) { + Map<String, Object> map = CastUtils.cast((Map<?, ?>)value); + return new UserAddress(map); + } else { + return null; + } + } + public void setUserAddressNumber(UserAddress address) { + setProperty(ADDRESS_CLAIM, address); + } + } http://git-wip-us.apache.org/repos/asf/cxf/blob/4ff4d39b/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 deleted file mode 100644 index eb16698..0000000 --- a/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/rp/IdTokenValidator.java +++ /dev/null @@ -1,121 +0,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. - */ -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/4ff4d39b/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 index 0a2e71d..10ece56 100644 --- 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 @@ -30,18 +30,25 @@ public final class OidcUtils { private OidcUtils() { } - public static void validateAccessTokenHash(ClientAccessToken at, JwtToken jwt) { + validateAccessTokenHash(at, jwt, true); + } + public static void validateAccessTokenHash(ClientAccessToken at, JwtToken jwt, boolean required) { validateHash(at.getTokenKey(), (String)jwt.getClaims().getClaim("at_hash"), - jwt.getHeaders().getAlgorithm()); + jwt.getHeaders().getAlgorithm(), + required); } public static void validateCodeHash(String code, JwtToken jwt) { + validateCodeHash(code, jwt, true); + } + public static void validateCodeHash(String code, JwtToken jwt, boolean required) { validateHash(code, (String)jwt.getClaims().getClaim("c_hash"), - jwt.getHeaders().getAlgorithm()); + jwt.getHeaders().getAlgorithm(), + required); } - private static void validateHash(String value, String theHash, String joseAlgo) { + private static void validateHash(String value, String theHash, String joseAlgo, boolean required) { String hash = calculateHash(value, joseAlgo); if (!hash.equals(theHash)) { throw new SecurityException("Invalid hash"); http://git-wip-us.apache.org/repos/asf/cxf/blob/4ff4d39b/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/rp/TokenValidator.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/rp/TokenValidator.java b/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/rp/TokenValidator.java new file mode 100644 index 0000000..483059e --- /dev/null +++ b/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/rp/TokenValidator.java @@ -0,0 +1,190 @@ +/** + * 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.util.concurrent.ConcurrentHashMap; + +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.JwtClaims; +import org.apache.cxf.rs.security.jose.jwt.JwtToken; +import org.apache.cxf.rs.security.oauth2.common.ClientAccessToken; +import org.apache.cxf.rs.security.oidc.common.UserIdToken; +import org.apache.cxf.rs.security.oidc.common.UserProfile; + +public class TokenValidator { + private JweDecryptionProvider jweDecryptor; + private JwsSignatureVerifier jwsVerifier; + private String issuerId; + private int issuedAtRange; + private boolean requireAtHash = true; + private WebClient jwkSetClient; + private ConcurrentHashMap<String, JsonWebKey> keyMap = new ConcurrentHashMap<String, JsonWebKey>(); + + public UserIdToken getIdTokenFromJwt(ClientAccessToken at, String clientId) { + JwtToken jwt = getIdJwtToken(at, clientId); + return getIdTokenFromJwt(jwt, clientId); + } + public UserIdToken getIdTokenFromJwt(JwtToken jwt, String clientId) { + //TODO: do the extra validation if needed + return new UserIdToken(jwt.getClaims().asMap()); + } + public JwtToken getIdJwtToken(ClientAccessToken at, String clientId) { + String idJwtToken = at.getParameters().get("id_token"); + JwtToken jwt = getJwtToken(idJwtToken, clientId); + validateJwtClaims(jwt.getClaims(), clientId, true); + OidcUtils.validateAccessTokenHash(at, jwt, requireAtHash); + return jwt; + } + public UserProfile getProfile(WebClient profileClient, UserIdToken idToken) { + return getProfile(profileClient, idToken, false); + } + public UserProfile getProfile(WebClient profileClient, UserIdToken idToken, boolean asJwt) { + if (asJwt) { + String jwt = profileClient.get(String.class); + return getProfileFromJwt(jwt, idToken); + } else { + UserProfile profile = profileClient.get(UserProfile.class); + validateUserProfile(profile, idToken); + return profile; + } + + } + public UserProfile getProfileFromJwt(String profileJwtToken, UserIdToken idToken) { + JwtToken jwt = getProfileJwtToken(profileJwtToken, idToken); + return getProfileFromJwt(jwt, idToken); + } + public UserProfile getProfileFromJwt(JwtToken jwt, UserIdToken idToken) { + UserProfile profile = new UserProfile(jwt.getClaims().asMap()); + validateUserProfile(profile, idToken); + return profile; + } + public JwtToken getProfileJwtToken(String profileJwtToken, UserIdToken idToken) { + return getJwtToken(profileJwtToken, idToken.getAudience()); + } + public void validateUserProfile(UserProfile profile, UserIdToken idToken) { + validateJwtClaims(profile, idToken.getAudience(), false); + // validate subject + if (!idToken.getSubject().equals(profile.getSubject())) { + throw new SecurityException("Invalid subject"); + } + } + public JwtToken getJwtToken(String wrappedJwtToken, String clientId) { + if (wrappedJwtToken == null) { + throw new SecurityException("ID Token is missing"); + } + // Decrypt the token if needed + if (jweDecryptor != null) { + wrappedJwtToken = jweDecryptor.decrypt(wrappedJwtToken).getContentText(); + } + + // read id_token into JwtToken + JwsJwtCompactConsumer jwtConsumer = new JwsJwtCompactConsumer(wrappedJwtToken); + JwtToken jwt = jwtConsumer.getJwtToken(); + // validate token signature + JwsSignatureVerifier theJwsVerifier = loadJwkSignatureVerifier(jwt); + if (!jwtConsumer.verifySignatureWith(theJwsVerifier)) { + throw new SecurityException("ID Token signature verification failed"); + } + return jwt; + } + + private void validateJwtClaims(JwtClaims claims, String clientId, boolean validateClaimsAlways) { + // validate subject + if (claims.getSubject() == null) { + throw new SecurityException("Invalid subject"); + } + // validate audience + String aud = claims.getAudience(); + if (aud == null && validateClaimsAlways || aud != null && !clientId.equals(aud)) { + throw new SecurityException("Invalid audience"); + } + + // validate the provider + String issuer = claims.getIssuer(); + if (issuerId == null && validateClaimsAlways || issuerId != null && !issuerId.equals(issuer)) { + throw new SecurityException("Invalid provider"); + } + Long currentTimeInSecs = System.currentTimeMillis() / 1000; + Long expiryTimeInSecs = claims.getExpiryTime(); + if (expiryTimeInSecs == null && validateClaimsAlways + || expiryTimeInSecs != null && currentTimeInSecs > expiryTimeInSecs) { + throw new SecurityException("The token expired"); + } + Long issuedAtInSecs = claims.getIssuedAt(); + if (issuedAtInSecs == null && validateClaimsAlways + || issuedAtInSecs != null && (issuedAtInSecs > currentTimeInSecs || issuedAtRange > 0 + && issuedAtInSecs < currentTimeInSecs - issuedAtRange)) { + throw new SecurityException("Invalid issuedAt"); + } + + } + + 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"); + } + JsonWebKey key = keyMap.get(keyId); + if (key == null) { + JsonWebKeys keys = jwkSetClient.get(JsonWebKeys.class); + key = keys.getKey(keyId); + keyMap.putIfAbsent(keyId, key); + } + 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; + } + + public void setRequireAtHash(boolean requireAtHash) { + this.requireAtHash = requireAtHash; + } +}
