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
The following commit(s) were added to refs/heads/main by this push:
new a98e46c19 bugfix(Guice): align Guice behavior regarding superclass
annotation p… (#2881)
a98e46c19 is described below
commit a98e46c1944b03b316dac880f876fb16cf4c8ca7
Author: Lenny Primak <[email protected]>
AuthorDate: Tue Sep 8 11:36:27 2026 -0500
bugfix(Guice): align Guice behavior regarding superclass annotation p…
(#2881)
---
.../shiro/aop/DefaultAnnotationResolver.java | 2 +-
.../org/apache/shiro/guice/aop/ShiroAopModule.java | 3 +-
.../aop/ClassLevelSuperclassAnnotatedTest.java | 98 ++++++++++++++++++++++
3 files changed, 101 insertions(+), 2 deletions(-)
diff --git
a/core/src/main/java/org/apache/shiro/aop/DefaultAnnotationResolver.java
b/core/src/main/java/org/apache/shiro/aop/DefaultAnnotationResolver.java
index c7784836b..3380beba2 100644
--- a/core/src/main/java/org/apache/shiro/aop/DefaultAnnotationResolver.java
+++ b/core/src/main/java/org/apache/shiro/aop/DefaultAnnotationResolver.java
@@ -68,7 +68,7 @@ public class DefaultAnnotationResolver implements
AnnotationResolver {
return annotation;
}
- private Annotation getAnnotationFromClassHierarchy(Class<?> targetClass,
Class<? extends Annotation> clazz) {
+ public static Annotation getAnnotationFromClassHierarchy(Class<?>
targetClass, Class<? extends Annotation> clazz) {
Class<?> current = targetClass;
while (current != null) {
Annotation annotation = current.getDeclaredAnnotation(clazz);
diff --git
a/support/guice/src/main/java/org/apache/shiro/guice/aop/ShiroAopModule.java
b/support/guice/src/main/java/org/apache/shiro/guice/aop/ShiroAopModule.java
index e26f73695..39500d014 100644
--- a/support/guice/src/main/java/org/apache/shiro/guice/aop/ShiroAopModule.java
+++ b/support/guice/src/main/java/org/apache/shiro/guice/aop/ShiroAopModule.java
@@ -30,6 +30,7 @@ import
org.apache.shiro.authz.aop.RoleAnnotationMethodInterceptor;
import org.apache.shiro.authz.aop.UserAnnotationMethodInterceptor;
import java.lang.annotation.Annotation;
+import static
org.apache.shiro.aop.DefaultAnnotationResolver.getAnnotationFromClassHierarchy;
/**
* Install this module to enable Shiro AOP functionality in Guice. You may
extend it to add your own Shiro
@@ -47,7 +48,7 @@ public class ShiroAopModule extends AbstractModule {
bindInterceptor(Matchers.any(), method -> {
Class<? extends Annotation> annotation =
methodInterceptor.getHandler().getAnnotationClass();
return method.getAnnotation(annotation) != null
- || method.getDeclaringClass().getAnnotation(annotation) !=
null;
+ ||
getAnnotationFromClassHierarchy(method.getDeclaringClass(), annotation) != null;
}, new AopAllianceMethodInterceptorAdapter(methodInterceptor));
}
diff --git
a/support/guice/src/test/java/org/apache/shiro/guice/aop/ClassLevelSuperclassAnnotatedTest.java
b/support/guice/src/test/java/org/apache/shiro/guice/aop/ClassLevelSuperclassAnnotatedTest.java
new file mode 100644
index 000000000..844177818
--- /dev/null
+++
b/support/guice/src/test/java/org/apache/shiro/guice/aop/ClassLevelSuperclassAnnotatedTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.guice.aop;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.authz.AuthorizationException;
+import org.apache.shiro.authz.annotation.RequiresRoles;
+import org.apache.shiro.mgt.DefaultSecurityManager;
+import org.apache.shiro.realm.SimpleAccountRealm;
+import org.apache.shiro.subject.Subject;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+class ClassLevelSuperclassAnnotatedTest {
+ private final Injector injector = Guice.createInjector(new
ShiroAopModule());
+
+ @Test
+ void overriddenMethodExecutesForUserWithoutRequiredRole() {
+ Subject subject = login("alice");
+ OverridingService service =
injector.getInstance(OverridingService.class);
+ subject.execute(() -> {
+ assertThatThrownBy(service::adminOnlyData)
+ .isInstanceOf(AuthorizationException.class);
+
+ assertThatThrownBy(service::directlyProtected)
+ .isInstanceOf(AuthorizationException.class);
+ });
+ }
+
+ @Test
+ void inheritedMethodStillExecutesTheSuperclassCheck() {
+ Subject subject = login("alice");
+ InheritedService service =
injector.getInstance(InheritedService.class);
+ subject.execute(() -> {
+ assertThatThrownBy(service::adminOnlyData)
+ .isInstanceOf(AuthorizationException.class);
+ });
+ }
+
+ @Test
+ void adminCanExecuteTheOverriddenMethod() {
+ Subject subject = login("admin");
+ OverridingService service =
injector.getInstance(OverridingService.class);
+ subject.execute(() -> {
+ assertThat(service.adminOnlyData()).isEqualTo("ADMIN_ONLY_DATA");
+ });
+ }
+
+ private Subject login(String username) {
+ SimpleAccountRealm realm = new SimpleAccountRealm();
+ realm.addAccount("alice", "pw", "user");
+ realm.addAccount("admin", "pw", "admin");
+ Subject subject = new Subject.Builder(new
DefaultSecurityManager(realm)).buildSubject();
+ subject.login(new UsernamePasswordToken(username, "pw"));
+ return subject;
+ }
+
+ @RequiresRoles("admin")
+ public static class ParentService {
+ public String adminOnlyData() {
+ return "PARENT_ADMIN_ONLY_DATA";
+ }
+ }
+
+ public static class OverridingService extends ParentService {
+ @Override
+ public String adminOnlyData() {
+ return "ADMIN_ONLY_DATA";
+ }
+
+ @RequiresRoles("admin")
+ public void directlyProtected() {
+ }
+ }
+
+ public static class InheritedService extends ParentService {
+ }
+}