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;
+    }
+}

Reply via email to