[java-idp-plugin-oidc-rp] branch main updated: Add UserInfo JWT claim validation
Phil Smart
philip.smart at jisc.ac.uk
Wed Jun 8 11:11:03 UTC 2022
This is an automated email from the git hooks/post-receive script.
philsmart pushed a commit to branch main
in repository java-idp-plugin-oidc-rp.
View the commit online:
http://git.shibboleth.net/view/?p=java-idp-plugin-oidc-rp.git;a=commit;h=8cfdad4b8d168be9deed1427c6640074a022da00
The following commit(s) were added to refs/heads/main by this push:
new 8cfdad4 Add UserInfo JWT claim validation
8cfdad4 is described below
commit 8cfdad4b8d168be9deed1427c6640074a022da00
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Wed Jun 8 12:10:58 2022 +0100
Add UserInfo JWT claim validation
---
.../navigate/SubFromIDTokenLookupFunction.java | 95 ++++++++++++++++++++++
.../authn/oidc/rp/messaging/UserInfoResponse.java | 2 +-
.../impl/DefaultUserInfoTokenLookupStrategy.java | 3 +-
.../authn/oidc/rp/impl/ValidateTokenClaims.java | 6 +-
.../oidc-relying-party-authn-beans.xml | 63 +++++++++-----
.../oidc-relying-party-authn-flow.xml | 9 +-
.../plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java | 2 +-
7 files changed, 148 insertions(+), 32 deletions(-)
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/SubFromIDTokenLookupFunction.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/SubFromIDTokenLookupFunction.java
new file mode 100644
index 0000000..5b4147c
--- /dev/null
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/SubFromIDTokenLookupFunction.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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 net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate;
+
+import java.text.ParseException;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * A function that pulls the subject 'sub' out of the id_token in the {@link AccessTokenResponseContext}.
+ */
+ at ThreadSafe
+public class SubFromIDTokenLookupFunction implements BiFunction<ProfileRequestContext, JWTClaimsSet, String> {
+
+ /** Class logger. */
+ @Nonnull
+ private final Logger log = LoggerFactory.getLogger(SubFromIDTokenLookupFunction.class);
+
+ /** Strategy used to locate the {@link AccessTokenResponseContext} to extract the id_token from. */
+ @Nonnull
+ private final Function<ProfileRequestContext, AccessTokenResponseContext> tokenResponseContextLookupStrategy;
+
+ /** Constructor. */
+ public SubFromIDTokenLookupFunction() {
+ tokenResponseContextLookupStrategy = new ChildContextLookup<>(AccessTokenResponseContext.class, true)
+ .compose(new InboundMessageContextLookup());
+ }
+
+ /**
+ *
+ * Constructor.
+ *
+ * @param strategy the AccessTokenResponseContext lookup strategy to use.
+ */
+ public SubFromIDTokenLookupFunction(
+ @Nonnull final Function<ProfileRequestContext, AccessTokenResponseContext> strategy) {
+ tokenResponseContextLookupStrategy =
+ Constraint.isNotNull(strategy, "AccessTokenResponseContext lookup strategy can not be null");
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * The input claims set is ignored because it belongs to the JWT that is being tested. Here we lookup
+ * the 'sub' claim from the id_token in a different context.
+ */
+ @Override
+ @Nullable
+ public String apply(@Nonnull final ProfileRequestContext prc, @Nullable final JWTClaimsSet claimsSet) {
+ final AccessTokenResponseContext tokenContext = tokenResponseContextLookupStrategy.apply(prc);
+ if (tokenContext == null || tokenContext.getIdToken() == null) {
+ return null;
+ }
+ try {
+ final JWTClaimsSet claims = tokenContext.getIdToken().getJWTClaimsSet();
+ if (claims.getSubject() != null) {
+ return claims.getSubject();
+ }
+ } catch (final ParseException e) {
+ log.warn("Unable to parse id_token claims, can not extract subjuect", e);
+ }
+ return null;
+ }
+
+}
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/UserInfoResponse.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/UserInfoResponse.java
index 9cb9927..4a39ca9 100644
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/UserInfoResponse.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/UserInfoResponse.java
@@ -28,7 +28,7 @@ import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
public interface UserInfoResponse {
/** Which type of response is this.*/
- enum UserInfoResponseType {
+ public enum UserInfoResponseType {
/** A plain JSON Object type.*/
PLAIN,
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultUserInfoTokenLookupStrategy.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultUserInfoTokenLookupStrategy.java
index 9910f35..f60abf3 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultUserInfoTokenLookupStrategy.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultUserInfoTokenLookupStrategy.java
@@ -32,6 +32,7 @@ import com.nimbusds.jwt.JWT;
import net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext;
import net.shibboleth.idp.plugin.authn.oidc.rp.context.UserInfoResponseContext;
import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.JWTUserInfoResponse;
+import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.UserInfoResponse.UserInfoResponseType;
import net.shibboleth.utilities.java.support.logic.Constraint;
/** Function that extracts the id_token from the {@link AccessTokenResponseContext}.*/
@@ -69,7 +70,7 @@ public class DefaultUserInfoTokenLookupStrategy implements Function<ProfileReque
final UserInfoResponseContext userInfoContext = userInfoResponseContextLookupStrategy.apply(prc);
if (userInfoContext == null || userInfoContext.getUserInfo() == null ||
- !(userInfoContext.getUserInfo() instanceof JWTUserInfoResponse)) {
+ userInfoContext.getUserInfo().getType() != UserInfoResponseType.JWT) {
return null;
}
return ((JWTUserInfoResponse)userInfoContext.getUserInfo()).getResponseJwt();
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateTokenClaims.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateTokenClaims.java
index b1a2bb9..94b7ad9 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateTokenClaims.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateTokenClaims.java
@@ -173,7 +173,7 @@ public class ValidateTokenClaims extends AbstractOIDCAuthenticationAction {
protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
@Nonnull final AuthenticationContext authenticationContext) {
- log.debug("{} Validating JWT token claims for subject '{}'",getLogPrefix(),
+ log.debug("{} Validating JWT claims for subject '{}'",getLogPrefix(),
claimsSet.getSubject() != null ? claimsSet.getSubject() : "unknown subject");
try {
@@ -182,7 +182,7 @@ public class ValidateTokenClaims extends AbstractOIDCAuthenticationAction {
cleanupHook.accept(profileRequestContext);
}
} catch (final JWTValidationException e) {
- log.error("{} JWT token verification failed for subject '{}'", getLogPrefix(),claimsSet.getSubject(),e);
+ log.error("{} JWT verification failed for subject '{}'", getLogPrefix(),claimsSet.getSubject(),e);
ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
if (cleanupHook != null) {
cleanupHook.accept(profileRequestContext);
@@ -190,7 +190,7 @@ public class ValidateTokenClaims extends AbstractOIDCAuthenticationAction {
return;
}
//fine.
- log.debug("{} JWT token claims are valid for subject '{}'",getLogPrefix(),claimsSet.getSubject());
+ log.debug("{} JWT claims are valid for subject '{}'",getLogPrefix(),claimsSet.getSubject());
}
diff --git a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-beans.xml b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-beans.xml
index 7c95664..26d175a 100644
--- a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-beans.xml
+++ b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-beans.xml
@@ -364,7 +364,7 @@
<bean id="shibboleth.SignatureValidationParametersResolver"
class="org.opensaml.xmlsec.impl.BasicSignatureValidationParametersResolver" />
- <!-- Default id_token JWT validation wiring. -->
+ <!-- Default id_token and some UserInfo JWT validation wiring. -->
<!-- No default cleanup, maybe could be to remove nonce etc. -->
<bean id="ValidateIDTokenClaims" parent="NestedWebFlowProfileActionAdaptor" scope="prototype"
@@ -583,27 +583,10 @@
</bean>
</constructor-arg>
</bean>
+
+ <!-- UserInfo response JWT validation -->
-
- <!-- UserInfo Decryption and Signature Validation Done -->
-
-
- <bean id="ValidateUserInfoClaims" parent="NestedWebFlowProfileActionAdaptor" scope="prototype"
- class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateUserInfoClaims"
- p:authenticationContextLookupStrategy-ref="ParentAuthenticiationContextLookup" />
-
-
- <bean id="ProcessEndUserClaims" class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ProcessEndUserClaims"
- scope="prototype" p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
- p:authenticationContextLookupStrategy-ref="ParentAuthenticiationContextLookup" />
-
-
- <bean id="CheckUserInfoPlainResponseTypeCondition"
- class="net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.logic.UserInfoPlainResponseTypeCondition" />
-
- <!-- UserInfo response JWT validation -->
-
- <bean id="ValidateUserInfoToken" parent="NestedWebFlowProfileActionAdaptor" scope="prototype"
+ <bean id="ValidateUserInfoTokenClaims" parent="NestedWebFlowProfileActionAdaptor" scope="prototype"
class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateTokenClaims"
p:authenticationContextLookupStrategy-ref="ParentAuthenticiationContextLookup"
p:cleanupHook="#{getObject('shibboleth.authn.oidc.rp.userinfo.jwt.claims.CleanUpHook')}"
@@ -620,12 +603,48 @@
class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.DefaultUserInfoTokenLookupStrategy" />
<util:list id="UserInfoClaimsValidators" value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
+ <ref bean="SubClaimRequiredValidator"/>
+ <ref bean="SubMatchesIDTokenClaimValidator"/>
<ref bean="IssuerClaimsValidator" />
<ref bean="AudienceClaimsValidator" />
- </util:list>
+ </util:list>
+
+ <bean id="SubClaimRequiredValidator"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator">
+ <property name="requiredClaims">
+ <list>
+ <value>sub</value>
+ </list>
+ </property>
+ </bean>
+
+ <bean id="SubMatchesIDTokenClaimValidator"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.ExactMatchClaimsValidator"
+ p:claimName="sub">
+ <property name="valueToMatchLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate.SubFromIDTokenLookupFunction"/>
+ </property>
+ </bean>
+
+
+
+ <!-- UserInfo Decryption and Signature Validation Done -->
+
+ <!-- TODO turn this into a ClaimsValidator actions -->
+ <bean id="ValidateUserInfoPlainResponseClaims" parent="NestedWebFlowProfileActionAdaptor" scope="prototype"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateUserInfoClaims"
+ p:authenticationContextLookupStrategy-ref="ParentAuthenticiationContextLookup" />
+ <bean id="ProcessEndUserClaims" class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ProcessEndUserClaims"
+ scope="prototype" p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
+ p:authenticationContextLookupStrategy-ref="ParentAuthenticiationContextLookup" />
+
+
+ <bean id="CheckUserInfoPlainResponseTypeCondition"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.logic.UserInfoPlainResponseTypeCondition" />
+
<!-- Final validation and proxy authentication result -->
<bean id="ValidateOIDCAuthentication" parent="NestedWebFlowProfileActionAdaptor"
diff --git a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml
index 911b391..807aa84 100644
--- a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml
+++ b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml
@@ -111,8 +111,8 @@
<evaluate expression="PopulateIDTokenDecryptionParameters" />
<evaluate expression="DecryptJWT"/>
<evaluate expression="PopulateIDTokenSignatureValidationParameters" />
+ <!-- TODO Not sure if HandleIDTokenValidation needs to be a message chain? -->
<evaluate expression="HandleIDTokenValidation" />
-
<evaluate expression="ValidateIDTokenClaims" />
<evaluate expression="'proceed'" />
<transition on="proceed" to="CheckUserInfoClaimsRequired" />
@@ -143,13 +143,14 @@
<evaluate expression="DecryptUserInfoJWT"/>
<evaluate expression="PopulateUserInfoTokenSignatureValidationParameters" />
<evaluate expression="HandleUserInfoTokenValidation" />
+ <evaluate expression="ValidateUserInfoTokenClaims" />
<evaluate expression="'proceed'" />
- <transition on="proceed" to="ValidateUserInfoClaimsSet" />
+ <transition on="proceed" to="FinalizeResponse" />
</action-state>
<!-- Plain UserInfo response types will skip straight to this stage -->
- <action-state id="ValidateUserInfoClaimsSet">
- <evaluate expression="ValidateUserInfoClaims" />
+ <action-state id="ValidateUserInfoPlaimClaimsSet">
+ <evaluate expression="ValidateUserInfoPlainResponseClaims" />
<evaluate expression="'proceed'" />
<transition on="proceed" to="FinalizeResponse" />
</action-state>
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java
index 346fdc8..525aae7 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java
@@ -884,7 +884,7 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
}
@Test
- public void testAuthnFlowFromAuthorizationCallback_Using_AsymetricSignedAnEncrypted_IDTokenAndUserInfoResponse()
+ public void testAuthnFlowFromAuthorizationCallback_Using_AsymetricSignedAndEncrypted_IDTokenAndUserInfoResponse()
throws Exception {
setFlowPath(FLOW);
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list