[java-idp-oidc] 02/02: JOIDC-236 - Audit logging for OAuth2Client login flow
Henri Mikkonen
henri.mikkonen at iki.fi
Tue Jun 10 10:56:55 UTC 2025
This is an automated email from the git hooks/post-receive script.
hjmikkon pushed a commit to branch main
in repository java-idp-oidc.
View the commit online:
http://git.shibboleth.net/view/?p=java-idp-oidc.git;a=commit;h=843ee7acbc1cfa5310da0087ae74975b9726415d
commit 843ee7acbc1cfa5310da0087ae74975b9726415d
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Tue Jun 10 13:56:28 2025 +0300
JOIDC-236 - Audit logging for OAuth2Client login flow
https://shibboleth.atlassian.net/browse/JOIDC-236
- Included audit logging support to the OAuth2Client authentication flow
- idp.authn.OAuth2Client.audit.enabled, defaults to idp.authn.audit.enabled (which defaults to false)
- category idp.authn.OAuth2Client.audit.category, defaults to Shibboleth-Audit.OAuth2Client
- formatting map configurable via idp.authn.OAuth2Client.audit.format, defaults to "%a|%T|%SP|%I|%s|%AF|%CV|%u|%tu|%AR|%UA" (same as in authn/Password)
- default extractors may be overridden via shibboleth.authn.OAuth2Client.AuditExtractors
- username (%u), transformed username (%tu), request JWT ID (%I), request JWT IAT (%D), request JWT audience (%aud)
- ClientAuthenticationJWTPayloadClaimsAuditExtractor can be used to extract desired claim from the incoming JWT client authentication payload
- constructor argument 'key' defines the claim name
---
...thenticationJWTPayloadClaimsAuditExtractor.java | 91 ++++++++++++++++++++++
.../oidc/op/authn/audit/impl/package-info.java | 16 ++++
.../authn/OAuth2Client/OAuth2Client-beans.xml | 53 ++++++++++++-
3 files changed, 159 insertions(+), 1 deletion(-)
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/authn/audit/impl/ClientAuthenticationJWTPayloadClaimsAuditExtractor.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/authn/audit/impl/ClientAuthenticationJWTPayloadClaimsAuditExtractor.java
new file mode 100644
index 00000000..1e320e26
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/authn/audit/impl/ClientAuthenticationJWTPayloadClaimsAuditExtractor.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed 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.oidc.op.authn.audit.impl;
+
+import java.text.ParseException;
+import java.util.Optional;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.oauth2.sdk.auth.JWTAuthentication;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.oidc.authn.context.OAuth2ClientAuthenticationContext;
+import net.shibboleth.shared.annotation.ParameterName;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.Constraint;
+
+/** {@link Function} that returns the desired claim from the client authentication JWT payload. */
+public class ClientAuthenticationJWTPayloadClaimsAuditExtractor implements Function<ProfileRequestContext, String> {
+
+ /** Strategy that will return {@link OAuth2ClientAuthenticationContext}. */
+ @Nonnull private Function<ProfileRequestContext,OAuth2ClientAuthenticationContext> clientAuthContextLookupStrategy;
+
+ /** The claim whose value is to be extracted. */
+ @Nonnull @NotEmpty private final String key;
+
+ /**
+ * Constructor.
+ *
+ * @param claim Claim whose value is to be extracted
+ */
+ public ClientAuthenticationJWTPayloadClaimsAuditExtractor(
+ @Nonnull @NotEmpty @ParameterName(name = "key") final String claim) {
+ key = Constraint.isNotEmpty(claim, "The claim cannot be empty");
+ // PRC -> AuthenticationContext -> OAuth2ClientAuthenticationContext
+ final Function<ProfileRequestContext,OAuth2ClientAuthenticationContext> cacls =
+ new ChildContextLookup<>(OAuth2ClientAuthenticationContext.class).compose(
+ new ChildContextLookup<>(AuthenticationContext.class));
+ assert cacls != null;
+ clientAuthContextLookupStrategy = cacls;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param claim Claim whose value is to be extracted
+ * @param lookupStrategy Strategy that will return {@link OAuth2ClientAuthenticationContext}.
+ */
+ public ClientAuthenticationJWTPayloadClaimsAuditExtractor(
+ @Nonnull @NotEmpty @ParameterName(name = "key") final String claim,
+ @Nonnull @ParameterName(name = "clientAuthContextLookupStrategy")
+ final Function<ProfileRequestContext,OAuth2ClientAuthenticationContext> lookupStrategy) {
+ key = Constraint.isNotEmpty(claim, "key cannot be empty");
+ clientAuthContextLookupStrategy =
+ Constraint.isNotNull(lookupStrategy, "clientAuthContextLookupStrategy lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Nullable public String apply(@Nullable final ProfileRequestContext input) {
+ final SignedJWT jwt = Optional.ofNullable(clientAuthContextLookupStrategy.apply(input))
+ .map(ctx -> ctx.getClientAuthentication())
+ .filter(JWTAuthentication.class::isInstance)
+ .map(JWTAuthentication.class::cast)
+ .map(jwtAuthentication -> jwtAuthentication.getClientAssertion())
+ .orElse(null);
+ try {
+ return jwt == null ? null : (String) jwt.getJWTClaimsSet().getClaim(key);
+ } catch (final ParseException e) {
+ return null;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/authn/audit/impl/package-info.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/authn/audit/impl/package-info.java
new file mode 100644
index 00000000..65ee5e57
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/authn/audit/impl/package-info.java
@@ -0,0 +1,16 @@
+/*
+ * Licensed 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 for audit extractors related to client authentication. */
+package net.shibboleth.idp.plugin.oidc.op.authn.audit.impl;
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OAuth2Client/OAuth2Client-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OAuth2Client/OAuth2Client-beans.xml
index eebd9585..5f4dd7b2 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OAuth2Client/OAuth2Client-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OAuth2Client/OAuth2Client-beans.xml
@@ -117,7 +117,9 @@
p:supportedPrincipals="#{getObject('shibboleth.authn.OAuth2Client.PrincipalOverride')}"
p:classifiedMessages="#{getObject('shibboleth.authn.OAuth2Client.ClassifiedMessageMap')}"
p:cleanupHook="#{T(java.lang.Boolean).valueOf('%{idp.authn.OAuth2Client.removeAfterValidation:true}') ? getObject('DefaultCleanupHook') : null}"
- p:lockoutManager="#{getObject('shibboleth.authn.OAuth2Client.AccountLockoutManager')}" />
+ p:lockoutManager="#{getObject('shibboleth.authn.OAuth2Client.AccountLockoutManager')}"
+ p:populateAuditContextAction="#{%{idp.authn.OAuth2Client.audit.enabled:%{idp.authn.audit.enabled:false}} ? getObject('shibboleth.authn.OAuth2Client.PopulateAuditContext') : null}"
+ p:writeAuditLogAction="#{%{idp.authn.OAuth2Client.audit.enabled:%{idp.authn.audit.enabled:false}} ? getObject('WriteAuthnAuditLog') : null}" />
<bean id="PopulateSubjectCanonicalizationContext"
class="net.shibboleth.idp.authn.impl.PopulateSubjectCanonicalizationContext" scope="prototype"
@@ -219,4 +221,53 @@
p:accountStateWarningPeriod="%{idp.authn.OAuth2Client.LDAP.accountStateWarningPeriod:#{null}}"
p:accountStateLoginFailures="%{idp.authn.OAuth2Client.LDAP.accountStateLoginFailures:0}" />
+ <util:map id="shibboleth.authn.AuditFormattingMap">
+ <entry key="#{'%{idp.authn.OAuth2Client.audit.category:Shibboleth-Audit.OAuth2Client}'.trim()}"
+ value="#{'%{idp.authn.OAuth2Client.audit.format:%a|%T|%SP|%I|%s|%AF|%CV|%u|%tu|%AR|%UA}'.trim()}" />
+ </util:map>
+
+ <bean id="shibboleth.authn.OAuth2Client.PopulateAuditContext" parent="shibboleth.authn.AbstractPopulateAuditContext" lazy-init="true"
+ p:fieldExtractors="#{getObject('shibboleth.authn.OAuth2Client.AuditExtractors') ?: getObject('shibboleth.authn.OAuth2Client.DefaultAuditExtractors')}"/>
+
+ <bean id="shibboleth.authn.OAuth2Client.DefaultAuditExtractors" parent="shibboleth.authn.DefaultAuditExtractors" lazy-init="true"
+ class="org.springframework.beans.factory.config.MapFactoryBean">
+ <property name="sourceMap">
+ <map merge="true">
+ <entry>
+ <key>
+ <util:constant static-field="net.shibboleth.idp.profile.IdPAuditFields.USERNAME"/>
+ </key>
+ <bean class="net.shibboleth.idp.authn.audit.impl.AttemptedUsernameAuditExtractor" />
+ </entry>
+ <entry>
+ <key>
+ <util:constant static-field="net.shibboleth.idp.authn.AuthnAuditFields.TRANSFORMED_USERNAME"/>
+ </key>
+ <bean class="net.shibboleth.idp.authn.audit.impl.TransformedUsernameAuditExtractor" />
+ </entry>
+ <entry>
+ <key>
+ <util:constant static-field="net.shibboleth.idp.saml.profile.SAMLAuditFields.REQUEST_ID"/>
+ </key>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.authn.audit.impl.ClientAuthenticationJWTPayloadClaimsAuditExtractor"
+ c:key="jti" />
+ </entry>
+ <entry>
+ <key>
+ <util:constant static-field="net.shibboleth.idp.saml.profile.SAMLAuditFields.REQUEST_ISSUE_INSTANT"/>
+ </key>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.authn.audit.impl.ClientAuthenticationJWTPayloadClaimsAuditExtractor"
+ c:key="iat" />
+ </entry>
+ <entry>
+ <key>
+ <util:constant static-field="net.shibboleth.idp.plugin.oidc.op.audit.AuditFields.AUDIENCE"/>
+ </key>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.authn.audit.impl.ClientAuthenticationJWTPayloadClaimsAuditExtractor"
+ c:key="aud" />
+ </entry>
+ </map>
+ </property>
+ </bean>
+
</beans>
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list