This is an automated email from the ASF dual-hosted git repository. lprimak pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/shiro.git
commit c7dc2a4d0f205f34b950247e5b82e7de24f56263 Author: lprimak <[email protected]> AuthorDate: Thu Jun 18 04:20:40 2026 -0500 enh: warn if realm authentication fails due to system exceptions in multi-realm environment, and send an event --- .../apache/shiro/authc/AbstractAuthenticator.java | 7 ++- .../authc/pam/AbstractAuthenticationStrategy.java | 27 ++++++++++ .../authc/pam/AuthenticationExceptionEvent.java | 41 +++++++++++++++ .../shiro/authc/pam/AuthenticationStrategy.java | 3 +- .../shiro/authc/pam/ModularRealmAuthenticator.java | 9 ++++ .../apache/shiro/mgt/SessionsSecurityManager.java | 17 ++++++ .../authc/pam/MultiRealmFailureEventTest.java | 61 ++++++++++++++++++++++ 7 files changed, 162 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/apache/shiro/authc/AbstractAuthenticator.java b/core/src/main/java/org/apache/shiro/authc/AbstractAuthenticator.java index 4c78affee..db648904d 100644 --- a/core/src/main/java/org/apache/shiro/authc/AbstractAuthenticator.java +++ b/core/src/main/java/org/apache/shiro/authc/AbstractAuthenticator.java @@ -18,6 +18,8 @@ */ package org.apache.shiro.authc; +import org.apache.shiro.event.EventBus; +import org.apache.shiro.event.EventBusAware; import org.apache.shiro.subject.PrincipalCollection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,7 +42,7 @@ import java.util.Collection; * * @since 0.1 */ -public abstract class AbstractAuthenticator implements Authenticator, LogoutAware { +public abstract class AbstractAuthenticator implements Authenticator, LogoutAware, EventBusAware { /*------------------------------------------- | C O N S T A N T S | @@ -258,5 +260,6 @@ public abstract class AbstractAuthenticator implements Authenticator, LogoutAwar protected abstract AuthenticationInfo doAuthenticate(AuthenticationToken token) throws AuthenticationException; - + @Override + public void setEventBus(EventBus eventBus) { } } diff --git a/core/src/main/java/org/apache/shiro/authc/pam/AbstractAuthenticationStrategy.java b/core/src/main/java/org/apache/shiro/authc/pam/AbstractAuthenticationStrategy.java index f8cb026fa..f93bc3cec 100644 --- a/core/src/main/java/org/apache/shiro/authc/pam/AbstractAuthenticationStrategy.java +++ b/core/src/main/java/org/apache/shiro/authc/pam/AbstractAuthenticationStrategy.java @@ -23,7 +23,10 @@ import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.MergableAuthenticationInfo; import org.apache.shiro.authc.SimpleAuthenticationInfo; +import org.apache.shiro.event.EventBus; import org.apache.shiro.realm.Realm; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.Collection; @@ -35,6 +38,9 @@ import java.util.Collection; * @since 0.9 */ public abstract class AbstractAuthenticationStrategy implements AuthenticationStrategy { + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractAuthenticationStrategy.class); + private EventBus eventBus; + private boolean warnIfAuthenticatorFailed = true; /** * Simply returns <code>new {@link org.apache.shiro.authc.SimpleAuthenticationInfo SimpleAuthenticationInfo}();</code>, @@ -64,6 +70,14 @@ public abstract class AbstractAuthenticationStrategy implements AuthenticationSt AuthenticationInfo info; if (singleRealmInfo == null) { info = aggregateInfo; + if (t != null && !(t instanceof AuthenticationException)) { + if (warnIfAuthenticatorFailed) { + LOGGER.warn("Error during multi-realm authentication for [" + realm + "]", t); + } + if (eventBus != null) { + eventBus.publish(new AuthenticationExceptionEvent(realm, t)); + } + } } else { if (aggregateInfo == null) { info = singleRealmInfo; @@ -103,4 +117,17 @@ public abstract class AbstractAuthenticationStrategy implements AuthenticationSt throws AuthenticationException { return aggregate; } + + @Override + public void setEventBus(EventBus bus) { + this.eventBus = bus; + } + + public boolean isWarnIfAuthenticatorFailed() { + return warnIfAuthenticatorFailed; + } + + public void setWarnIfAuthenticatorFailed(boolean warnIfAuthenticatorFailed) { + this.warnIfAuthenticatorFailed = warnIfAuthenticatorFailed; + } } diff --git a/core/src/main/java/org/apache/shiro/authc/pam/AuthenticationExceptionEvent.java b/core/src/main/java/org/apache/shiro/authc/pam/AuthenticationExceptionEvent.java new file mode 100644 index 000000000..cac00f19f --- /dev/null +++ b/core/src/main/java/org/apache/shiro/authc/pam/AuthenticationExceptionEvent.java @@ -0,0 +1,41 @@ +/* + * 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.shiro.authc.pam; + +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.event.Event; + +/** + * This event is triggered when an unexpected system exception (anything other than {@link AuthenticationException}) + * or its subclasses, is thrown during multi-realm authentication process. + * + * @since 3.0.0 + */ +public class AuthenticationExceptionEvent extends Event { + private final Throwable exception; + + public AuthenticationExceptionEvent(Object source, Throwable t) { + super(source); + this.exception = t; + } + + public Throwable getException() { + return exception; + } +} diff --git a/core/src/main/java/org/apache/shiro/authc/pam/AuthenticationStrategy.java b/core/src/main/java/org/apache/shiro/authc/pam/AuthenticationStrategy.java index 0231b6212..946e58dee 100644 --- a/core/src/main/java/org/apache/shiro/authc/pam/AuthenticationStrategy.java +++ b/core/src/main/java/org/apache/shiro/authc/pam/AuthenticationStrategy.java @@ -21,6 +21,7 @@ package org.apache.shiro.authc.pam; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.event.EventBusAware; import org.apache.shiro.realm.Realm; import java.util.Collection; @@ -39,7 +40,7 @@ import java.util.Collection; * @see FirstSuccessfulStrategy * @since 0.2 */ -public interface AuthenticationStrategy { +public interface AuthenticationStrategy extends EventBusAware { /** * Method invoked by the ModularAuthenticator signifying that the authentication process is about to begin for the diff --git a/core/src/main/java/org/apache/shiro/authc/pam/ModularRealmAuthenticator.java b/core/src/main/java/org/apache/shiro/authc/pam/ModularRealmAuthenticator.java index 596588130..b635ba195 100644 --- a/core/src/main/java/org/apache/shiro/authc/pam/ModularRealmAuthenticator.java +++ b/core/src/main/java/org/apache/shiro/authc/pam/ModularRealmAuthenticator.java @@ -24,6 +24,7 @@ import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.LogoutAware; import org.apache.shiro.authc.UnknownAccountException; +import org.apache.shiro.event.EventBus; import org.apache.shiro.realm.Realm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.CollectionUtils; @@ -300,4 +301,12 @@ public class ModularRealmAuthenticator extends AbstractAuthenticator { } } } + + /** + * {@inheritDoc} + */ + @Override + public void setEventBus(EventBus eventBus) { + authenticationStrategy.setEventBus(eventBus); + } } diff --git a/core/src/main/java/org/apache/shiro/mgt/SessionsSecurityManager.java b/core/src/main/java/org/apache/shiro/mgt/SessionsSecurityManager.java index b26c999cf..a681ac39f 100644 --- a/core/src/main/java/org/apache/shiro/mgt/SessionsSecurityManager.java +++ b/core/src/main/java/org/apache/shiro/mgt/SessionsSecurityManager.java @@ -117,6 +117,7 @@ public abstract class SessionsSecurityManager extends AuthorizingSecurityManager protected void afterEventBusSet() { super.afterEventBusSet(); applyEventBusToSessionManager(); + applyEventBusToAuthenticator(); } /** @@ -148,6 +149,22 @@ public abstract class SessionsSecurityManager extends AuthorizingSecurityManager } } + /** + * Ensures the internal delegate <code>Authenticator</code> is injected with the newly set + * {@link #setEventBus EventBus} so it may use it for its internal event needs. + * <p/> + * Note: This implementation only injects the EventBus into the Authenticator if it + * instance implements the {@link EventBusAware EventBusAware} interface. + * + * @since 3.0.0 + */ + protected void applyEventBusToAuthenticator() { + EventBus eventBus = getEventBus(); + if (eventBus != null && this.getAuthenticator() instanceof EventBusAware aware) { + aware.setEventBus(eventBus); + } + } + public Session start(SessionContext context) throws AuthorizationException { if (sessionManager == null) { throw new IllegalStateException("Session manager is not available or has been destroyed"); diff --git a/core/src/test/java/org/apache/shiro/authc/pam/MultiRealmFailureEventTest.java b/core/src/test/java/org/apache/shiro/authc/pam/MultiRealmFailureEventTest.java new file mode 100644 index 000000000..570c5d5a0 --- /dev/null +++ b/core/src/test/java/org/apache/shiro/authc/pam/MultiRealmFailureEventTest.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.shiro.authc.pam; + +import org.apache.shiro.authc.AbstractAuthenticator; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.AuthenticationInfo; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.authc.UsernamePasswordToken; +import org.apache.shiro.event.Subscribe; +import org.apache.shiro.mgt.DefaultSecurityManager; +import org.apache.shiro.realm.AuthenticatingRealm; +import org.apache.shiro.subject.Subject; +import org.junit.jupiter.api.Test; +import java.util.List; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +public class MultiRealmFailureEventTest extends AuthenticatingRealm { + private boolean eventReceived; + + @Test + public void realmFailureEvent() { + var securityManager = new DefaultSecurityManager(); + securityManager.setRealms(List.of(this, this)); + securityManager.getEventBus().register(this); + if (securityManager.getAuthenticator() instanceof AbstractAuthenticator authenticator) { + authenticator.setEventBus(securityManager.getEventBus()); + } + var subject = new Subject.Builder(securityManager).buildSubject(); + assertThatExceptionOfType(AuthenticationException.class) + .isThrownBy(() -> subject.login(new UsernamePasswordToken("user", "password"))); + assertThat(eventReceived).isTrue(); + } + + @Override + protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { + throw new IllegalStateException("Realm failure"); + } + + @Subscribe + public void handleAuthenticationException(AuthenticationExceptionEvent event) { + eventReceived = true; + } +}
