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 2016/02/24 18:01:52 UTC
[4/5] cxf-fediz git commit: Make things in the OIDC protocol handler
properly configurable
Make things in the OIDC protocol handler properly configurable
Project: http://git-wip-us.apache.org/repos/asf/cxf-fediz/repo
Commit: http://git-wip-us.apache.org/repos/asf/cxf-fediz/commit/9375d3e5
Tree: http://git-wip-us.apache.org/repos/asf/cxf-fediz/tree/9375d3e5
Diff: http://git-wip-us.apache.org/repos/asf/cxf-fediz/diff/9375d3e5
Branch: refs/heads/master
Commit: 9375d3e5465ff157b1cb8a463f34ee64e2bee78f
Parents: 88afda3
Author: Colm O hEigeartaigh <co...@apache.org>
Authored: Wed Feb 24 15:57:58 2016 +0000
Committer: Colm O hEigeartaigh <co...@apache.org>
Committed: Wed Feb 24 15:57:58 2016 +0000
----------------------------------------------------------------------
.../TrustedIdpOIDCProtocolHandler.java | 152 +++++++++++++++----
.../oidc/src/test/resources/entities-realma.xml | 22 +--
2 files changed, 124 insertions(+), 50 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/9375d3e5/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/protocols/TrustedIdpOIDCProtocolHandler.java
----------------------------------------------------------------------
diff --git a/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/protocols/TrustedIdpOIDCProtocolHandler.java b/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/protocols/TrustedIdpOIDCProtocolHandler.java
index 52e007e..1e1c199 100644
--- a/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/protocols/TrustedIdpOIDCProtocolHandler.java
+++ b/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/protocols/TrustedIdpOIDCProtocolHandler.java
@@ -30,6 +30,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
+import java.util.Map;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
@@ -57,6 +58,7 @@ 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.jwt.JwtConstants;
import org.apache.cxf.rs.security.jose.jwt.JwtToken;
+import org.apache.cxf.rs.security.jose.jwt.JwtUtils;
import org.apache.cxf.rs.security.oauth2.common.ClientAccessToken;
import org.apache.cxf.rs.security.oauth2.provider.OAuthJSONProvider;
import org.apache.cxf.rs.security.oauth2.utils.OAuthConstants;
@@ -81,6 +83,32 @@ import org.springframework.webflow.execution.RequestContext;
@Component
public class TrustedIdpOIDCProtocolHandler implements TrustedIdpProtocolHandler {
+ /**
+ * The client_id value to send to the OIDC IdP.
+ */
+ public static final String CLIENT_ID = "client.id";
+
+ /**
+ * The secret associated with the client to authenticate to the OIDC IdP.
+ */
+ public static final String CLIENT_SECRET = "client.secret";
+
+ /**
+ * The Token endpoint. The authorization endpoint is specified by TrustedIdp.url.
+ */
+ public static final String TOKEN_ENDPOINT = "token.endpoint";
+
+ /**
+ * The signature algorithm to use in verifying the IdToken. The default is "RS256".
+ */
+ public static final String SIGNATURE_ALGORITHM = "signature.algorithm";
+
+ /**
+ * The Claim in which to extract the Subject username to insert into the generated SAML token.
+ * It defaults to "preferred_username", otherwise it falls back to the "sub" claim.
+ */
+ public static final String SUBJECT_CLAIM = "subject.claim";
+
public static final String PROTOCOL = "openid-connect-1.0";
private static final Logger LOG = LoggerFactory.getLogger(TrustedIdpOIDCProtocolHandler.class);
@@ -99,15 +127,21 @@ public class TrustedIdpOIDCProtocolHandler implements TrustedIdpProtocolHandler
@Override
public URL mapSignInRequest(RequestContext context, Idp idp, TrustedIdp trustedIdp) {
+ String clientId = getProperty(trustedIdp, CLIENT_ID);
+ if (clientId == null || clientId.isEmpty()) {
+ LOG.warn("A CLIENT_ID must be configured to use the OIDCProtocolHandler");
+ throw new IllegalStateException("No CLIENT_ID specified");
+ }
+
try {
StringBuilder sb = new StringBuilder();
sb.append(trustedIdp.getUrl());
sb.append("?");
sb.append("response_type").append('=');
- sb.append("code"); //TODO
+ sb.append("code");
sb.append("&");
sb.append("client_id").append('=');
- sb.append("consumer-id"); //TODO
+ sb.append(clientId);
sb.append("&");
sb.append("redirect_uri").append('=');
sb.append(URLEncoder.encode(idp.getIdpUrl().toString(), "UTF-8"));
@@ -121,13 +155,6 @@ public class TrustedIdpOIDCProtocolHandler implements TrustedIdpProtocolHandler
sb.append(wctx);
}
- /*
- String wfresh = context.getFlowScope().getString(FederationConstants.PARAM_FRESHNESS);
- if (wfresh != null) {
- sb.append("&").append(FederationConstants.PARAM_FRESHNESS).append('=');
- sb.append(URLEncoder.encode(wfresh, "UTF-8"));
- }
- */
return new URL(sb.toString());
} catch (MalformedURLException ex) {
LOG.error("Invalid Redirect URL for Trusted Idp", ex);
@@ -143,27 +170,41 @@ public class TrustedIdpOIDCProtocolHandler implements TrustedIdpProtocolHandler
String code = (String) WebUtils.getAttributeFromFlowScope(context,
OAuthConstants.CODE_RESPONSE_TYPE);
- if (code != null) {
- // Here we need to get the IdToken using the authorization code
- String address = "http://localhost:8080/auth/realms/realmb/protocol/openid-connect/token";
+ if (code != null && !code.isEmpty()) {
+
+ String tokenEndpoint = getProperty(trustedIdp, TOKEN_ENDPOINT);
+ if (tokenEndpoint == null || tokenEndpoint.isEmpty()) {
+ LOG.warn("A TOKEN_ENDPOINT must be configured to use the OIDCProtocolHandler");
+ throw new IllegalStateException("No TOKEN_ENDPOINT specified");
+ }
+
+ String clientId = getProperty(trustedIdp, CLIENT_ID);
+ String clientSecret = getProperty(trustedIdp, CLIENT_SECRET);
+ if (clientSecret == null || clientSecret.isEmpty()) {
+ LOG.warn("A CLIENT_SECRET must be configured to use the OIDCProtocolHandler");
+ throw new IllegalStateException("No CLIENT_SECRET specified");
+ }
+ // Here we need to get the IdToken using the authorization code
List<Object> providers = new ArrayList<Object>();
providers.add(new OAuthJSONProvider());
WebClient client =
- WebClient.create(address, providers, "consumer-id", "90d5da25-e900-443f-a5d5-feb3bb060800", null);
+ WebClient.create(tokenEndpoint, providers, clientId, clientSecret, null);
ClientConfiguration config = WebClient.getConfig(client);
- config.getOutInterceptors().add(new LoggingOutInterceptor());
- config.getInInterceptors().add(new LoggingInInterceptor());
+ if (LOG.isDebugEnabled()) {
+ config.getOutInterceptors().add(new LoggingOutInterceptor());
+ config.getInInterceptors().add(new LoggingInInterceptor());
+ }
client.type("application/x-www-form-urlencoded").accept("application/json");
Form form = new Form();
form.param("grant_type", "authorization_code");
form.param("code", code);
- form.param("client_id", "consumer-id");
+ form.param("client_id", clientId);
form.param("redirect_uri", idp.getIdpUrl().toString());
Response response = client.post(form);
@@ -192,23 +233,24 @@ public class TrustedIdpOIDCProtocolHandler implements TrustedIdpProtocolHandler
JwsJwtCompactConsumer jwtConsumer = new JwsJwtCompactConsumer(idToken);
JwtToken jwt = jwtConsumer.getJwtToken();
- if (!jwtConsumer.verifySignatureWith(validatingCert, SignatureAlgorithm.RS256)) {
+ // Validate the Signature
+ String sigAlgo = getProperty(trustedIdp, SIGNATURE_ALGORITHM);
+ if (sigAlgo == null || sigAlgo.isEmpty()) {
+ sigAlgo = "RS256";
+ }
+ if (!jwtConsumer.verifySignatureWith(validatingCert, SignatureAlgorithm.getAlgorithm(sigAlgo))) {
LOG.warn("Signature does not validate");
return null;
}
- Date created = new Date();
- if (jwt.getClaim(JwtConstants.CLAIM_ISSUED_AT) != null) {
- created = new Date((long)jwt.getClaim(JwtConstants.CLAIM_ISSUED_AT) * 1000L);
- }
- if (jwt.getClaim(JwtConstants.CLAIM_EXPIRY) == null) {
- LOG.warn("No expiry in the token");
- return null;
- }
+ // Make sure the received token is valid according to the spec
+ validateToken(jwt, clientId);
+
+ Date created = new Date((long)jwt.getClaim(JwtConstants.CLAIM_ISSUED_AT) * 1000L);
Date expires = new Date((long)jwt.getClaim(JwtConstants.CLAIM_EXPIRY) * 1000L);
// Convert into a SAML Token
- SamlAssertionWrapper assertion = createSamlAssertion(idp, jwt, created, expires);
+ SamlAssertionWrapper assertion = createSamlAssertion(idp, trustedIdp, jwt, created, expires);
Document doc = DOMUtils.createDocument();
Element token = assertion.toDOM(doc);
@@ -234,6 +276,33 @@ public class TrustedIdpOIDCProtocolHandler implements TrustedIdpProtocolHandler
return null;
}
+ protected void validateToken(JwtToken jwt, String clientId) {
+ // We must have the following claims
+ if (jwt.getClaim(JwtConstants.CLAIM_ISSUER) == null
+ || jwt.getClaim(JwtConstants.CLAIM_SUBJECT) == null
+ || jwt.getClaim(JwtConstants.CLAIM_AUDIENCE) == null
+ || jwt.getClaim(JwtConstants.CLAIM_EXPIRY) == null
+ || jwt.getClaim(JwtConstants.CLAIM_ISSUED_AT) == null) {
+ LOG.warn("The IdToken is missing a required claim");
+ throw new IllegalStateException("The IdToken is missing a required claim");
+ }
+
+ // The audience must match the client_id of this client
+ boolean match = false;
+ for (String audience : jwt.getClaims().getAudiences()) {
+ if (clientId.equals(audience)) {
+ match = true;
+ break;
+ }
+ }
+ if (!match) {
+ LOG.warn("The audience of the token does not match this client");
+ throw new IllegalStateException("The audience of the token does not match this client");
+ }
+
+ JwtUtils.validateTokenClaims(jwt.getClaims(), 300, 0, false);
+ }
+
private Crypto getCrypto(String certificate) throws ProcessingException {
if (certificate == null) {
return null;
@@ -292,16 +361,29 @@ public class TrustedIdpOIDCProtocolHandler implements TrustedIdpProtocolHandler
}
}
- private SamlAssertionWrapper createSamlAssertion(Idp idp, JwtToken token,
+ protected SamlAssertionWrapper createSamlAssertion(Idp idp, TrustedIdp trustedIdp, JwtToken token,
Date created,
Date expires) throws Exception {
SamlCallbackHandler callbackHandler = new SamlCallbackHandler();
- callbackHandler.setIssuer(idp.getServiceDisplayName());
+ String issuer = idp.getServiceDisplayName();
+ if (issuer == null) {
+ issuer = idp.getRealm();
+ }
+ if (issuer != null) {
+ callbackHandler.setIssuer(issuer);
+ }
// Subject
- // TODO
+ String subjectName = getProperty(trustedIdp, SUBJECT_CLAIM);
+ if (subjectName == null || token.getClaim(subjectName) == null) {
+ subjectName = "preferred_username";
+ if (subjectName == null || token.getClaim(subjectName) == null) {
+ subjectName = JwtConstants.CLAIM_SUBJECT;
+ }
+ }
+
SubjectBean subjectBean =
- new SubjectBean((String)token.getClaim("preferred_username"),
+ new SubjectBean((String)token.getClaim(subjectName),
SAML2Constants.NAMEID_FORMAT_UNSPECIFIED,
SAML2Constants.CONF_BEARER);
callbackHandler.setSubjectBean(subjectBean);
@@ -329,6 +411,16 @@ public class TrustedIdpOIDCProtocolHandler implements TrustedIdpProtocolHandler
return assertion;
}
+ private String getProperty(TrustedIdp trustedIdp, String property) {
+ Map<String, String> parameters = trustedIdp.getParameters();
+
+ if (parameters != null && parameters.containsKey(property)) {
+ return parameters.get(property);
+ }
+
+ return null;
+ }
+
private static class SamlCallbackHandler implements CallbackHandler {
private ConditionsBean conditionsBean;
private SubjectBean subjectBean;
http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/9375d3e5/systests/federation/oidc/src/test/resources/entities-realma.xml
----------------------------------------------------------------------
diff --git a/systests/federation/oidc/src/test/resources/entities-realma.xml b/systests/federation/oidc/src/test/resources/entities-realma.xml
index ab17601..54a2855 100644
--- a/systests/federation/oidc/src/test/resources/entities-realma.xml
+++ b/systests/federation/oidc/src/test/resources/entities-realma.xml
@@ -73,7 +73,6 @@
<property name="trustedIdps">
<util:list>
<ref bean="trusted-idp-realmB" />
- <ref bean="trusted-idp-realmC" />
</util:list>
</property>
<property name="claimTypesOffered">
@@ -100,25 +99,8 @@
<property name="description" value="Realm B description" />
<property name="parameters">
<util:map>
- </util:map>
- </property>
- </bean>
-
- <bean id="trusted-idp-realmC"
- class="org.apache.cxf.fediz.service.idp.service.jpa.TrustedIdpEntity">
- <property name="realm" value="urn:org:apache:cxf:fediz:idp:realm-C" />
- <property name="cacheTokens" value="true" />
- <property name="url" value="https://localhost:${idp.samlsso.https.port}/idp/samlsso" />
- <property name="certificate" value="realmb.cert" />
- <property name="trustType" value="PEER_TRUST" />
- <property name="protocol" value="urn:oasis:names:tc:SAML:2.0:profiles:SSO:browser" />
- <property name="federationType" value="FEDERATE_IDENTITY" />
- <property name="name" value="Realm C" />
- <property name="description" value="SAML Web Profile - Response POST Binding" />
- <property name="parameters">
- <util:map>
- <entry key="sign.request" value="true" />
- <entry key="support.deflate.encoding" value="true" />
+ <entry key="client.id" value="consumer-id"/>
+ <entry key="token.endpoint" value="http://localhost:8080/auth/realms/realmb/protocol/openid-connect/token"/>
</util:map>
</property>
</bean>