You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shiro.apache.org by bd...@apache.org on 2019/12/05 22:24:26 UTC

[shiro] branch jsr-250 created (now c6d547d)

This is an automated email from the ASF dual-hosted git repository.

bdemers pushed a change to branch jsr-250
in repository https://gitbox.apache.org/repos/asf/shiro.git.


      at c6d547d  Add support for JSR-250 annotations DenyAll, PermitAll, RolesAllowed

This branch includes the following new commits:

     new c6d547d  Add support for JSR-250 annotations DenyAll, PermitAll, RolesAllowed

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[shiro] 01/01: Add support for JSR-250 annotations DenyAll, PermitAll, RolesAllowed

Posted by bd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

bdemers pushed a commit to branch jsr-250
in repository https://gitbox.apache.org/repos/asf/shiro.git

commit c6d547dc6af218f088a06001da8dc0ff8cec7abd
Author: Brian Demers <bd...@apache.org>
AuthorDate: Thu Dec 5 17:23:21 2019 -0500

    Add support for JSR-250 annotations DenyAll, PermitAll, RolesAllowed
---
 core/pom.xml                                       |   7 +
 .../shiro/aop/DefaultAnnotationResolver.java       |   3 +
 .../AnnotationsAuthorizingMethodInterceptor.java   |   7 +-
 .../shiro/authz/aop/DenyAllAnnotationHandler.java  |  57 +++++++
 .../shiro/authz/aop/Jsr250MethodInterceptor.java   | 105 ++++++++++++
 .../authz/aop/PermitAllAnnotationHandler.java      |  49 ++++++
 .../authz/aop/RolesAllowedAnnotationHandler.java   |  70 ++++++++
 .../authz/aop/DenyAllAnnotationHandlerTest.java    |  72 ++++++++
 .../Jsr250AnnotationHandlerInteractionTest.java    | 183 +++++++++++++++++++++
 .../authz/aop/PermitAllAnnotationHandlerTest.java  |  46 ++++++
 .../aop/RolesAllowedAnnotationHandlerTest.java     |  97 +++++++++++
 11 files changed, 695 insertions(+), 1 deletion(-)

diff --git a/core/pom.xml b/core/pom.xml
index 73e6211..513eb4e 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -57,6 +57,7 @@
                             org.apache.shiro*;version="${shiro.osgi.importRange}",
                             org.apache.commons.beanutils*;resolution:=optional,
                             org.apache.commons.configuration2*;resolution:=optional,
+                            javax.annotation.security*;resolution:=optional,
                             *
                         </Import-Package>
                         <DynamicImport-Package>
@@ -98,6 +99,12 @@
             <artifactId>shiro-event</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>javax.annotation</groupId>
+            <artifactId>javax.annotation-api</artifactId>
+            <version>1.3.2</version>
+        </dependency>
+
         <!-- Test dependencies -->
         <dependency>
             <groupId>org.slf4j</groupId>
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 d7fd4ae..84671ca 100644
--- a/core/src/main/java/org/apache/shiro/aop/DefaultAnnotationResolver.java
+++ b/core/src/main/java/org/apache/shiro/aop/DefaultAnnotationResolver.java
@@ -58,7 +58,10 @@ public class DefaultAnnotationResolver implements AnnotationResolver {
             throw new IllegalArgumentException(msg);
 
         }
+        // first look for the Annotation on the method
         Annotation annotation = m.getAnnotation(clazz);
+
+        // then look on the class
         if (annotation == null ) {
             Object miThis = mi.getThis();
             //SHIRO-473 - miThis could be null for static methods, just return null
diff --git a/core/src/main/java/org/apache/shiro/authz/aop/AnnotationsAuthorizingMethodInterceptor.java b/core/src/main/java/org/apache/shiro/authz/aop/AnnotationsAuthorizingMethodInterceptor.java
index f9bd41a..661d3f3 100644
--- a/core/src/main/java/org/apache/shiro/authz/aop/AnnotationsAuthorizingMethodInterceptor.java
+++ b/core/src/main/java/org/apache/shiro/authz/aop/AnnotationsAuthorizingMethodInterceptor.java
@@ -23,6 +23,7 @@ import java.util.Collection;
 
 import org.apache.shiro.aop.MethodInvocation;
 import org.apache.shiro.authz.AuthorizationException;
+import org.apache.shiro.util.ClassUtils;
 
 /**
  * An <tt>AnnotationsAuthorizingMethodInterceptor</tt> is a MethodInterceptor that asserts a given method is authorized
@@ -52,12 +53,16 @@ public abstract class AnnotationsAuthorizingMethodInterceptor extends Authorizin
      * support role and permission annotations.
      */
     public AnnotationsAuthorizingMethodInterceptor() {
-        methodInterceptors = new ArrayList<AuthorizingAnnotationMethodInterceptor>(5);
+        methodInterceptors = new ArrayList<>(8);
         methodInterceptors.add(new RoleAnnotationMethodInterceptor());
         methodInterceptors.add(new PermissionAnnotationMethodInterceptor());
         methodInterceptors.add(new AuthenticatedAnnotationMethodInterceptor());
         methodInterceptors.add(new UserAnnotationMethodInterceptor());
         methodInterceptors.add(new GuestAnnotationMethodInterceptor());
+
+        if (ClassUtils.isAvailable("javax.annotation.security.PermitAll")) {
+            methodInterceptors.add(new Jsr250MethodInterceptor());
+        }
     }
 
     /**
diff --git a/core/src/main/java/org/apache/shiro/authz/aop/DenyAllAnnotationHandler.java b/core/src/main/java/org/apache/shiro/authz/aop/DenyAllAnnotationHandler.java
new file mode 100644
index 0000000..0b8fb00
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/aop/DenyAllAnnotationHandler.java
@@ -0,0 +1,57 @@
+/*
+ * 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.authz.aop;
+
+import org.apache.shiro.authz.UnauthenticatedException;
+import org.apache.shiro.authz.UnauthorizedException;
+
+import javax.annotation.security.DenyAll;
+import java.lang.annotation.Annotation;
+
+/**
+ * This {@link org.apache.shiro.aop.AnnotationHandler AnnotationHandler} denys access from any subject
+ * (anonymous or logged in user).
+ *
+ * @since 1.5.0
+ */
+public class DenyAllAnnotationHandler extends AuthorizingAnnotationHandler {
+
+
+    /**
+     * Default no-argument constructor that ensures this interceptor looks for
+     *
+     * {@link org.apache.shiro.authz.annotation.RequiresGuest RequiresGuest} annotations in a method
+     * declaration.
+     */
+    public DenyAllAnnotationHandler() {
+        super(DenyAll.class);
+    }
+
+    /**
+     * Causes a {@link UnauthorizedException} to be thrown if a DenyAll annotation is present.
+     *
+     * @param a the annotation to check for one or more roles
+     * @throws UnauthorizedException when the DenyAll annotation is present
+     */
+    public void assertAuthorized(Annotation a) throws UnauthorizedException {
+//        if (!(a instanceof DenyAll)) return;
+
+        throw new UnauthenticatedException("Attempting to perform a denied operation.");
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/aop/Jsr250MethodInterceptor.java b/core/src/main/java/org/apache/shiro/authz/aop/Jsr250MethodInterceptor.java
new file mode 100644
index 0000000..ae7b6bc
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/aop/Jsr250MethodInterceptor.java
@@ -0,0 +1,105 @@
+/*
+ * 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.authz.aop;
+
+import org.apache.shiro.aop.MethodInterceptorSupport;
+import org.apache.shiro.aop.MethodInvocation;
+
+import javax.annotation.security.DenyAll;
+import javax.annotation.security.PermitAll;
+import javax.annotation.security.RolesAllowed;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
+public class Jsr250MethodInterceptor extends AuthorizingAnnotationMethodInterceptor {
+
+    private final Map<Class<? extends Annotation>, AuthorizingAnnotationHandler> handlerMap = new HashMap<>();
+
+    public Jsr250MethodInterceptor() {
+
+        // NOTE: in order for this package to retain backwards compatibility, it MUST extend AuthorizingAnnotationMethodInterceptor
+        // which only supports a single Handler.  JSR 250 annotations require knowledge of other annotations (PermitAll on a method
+        // overrides DenyAll at the class level).  Using the most restrictive handler here as a place holder.
+        super(new DenyAllAnnotationHandler());
+
+        handlerMap.put(DenyAll.class, new DenyAllAnnotationHandler());
+        handlerMap.put(PermitAll.class, new PermitAllAnnotationHandler());
+        handlerMap.put(RolesAllowed.class, new RolesAllowedAnnotationHandler());
+    }
+
+    @Override
+    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
+        assertAuthorized(methodInvocation);
+        return methodInvocation.proceed();
+    }
+
+    @Override
+    public void assertAuthorized(MethodInvocation methodInvocation) {
+        if (methodInvocation == null) {
+            throw new IllegalArgumentException("method argument cannot be null");
+        }
+
+        Annotation annotation = getAnnotation(methodInvocation);
+        if (annotation != null) {
+            AuthorizingAnnotationHandler handler = handlerMap.get(annotation.annotationType());
+            handler.assertAuthorized(annotation);
+        }
+    }
+
+    @Override
+    public Annotation getAnnotation(MethodInvocation methodInvocation) {
+
+        Method method = methodInvocation.getMethod();
+        if (method == null) {
+            String msg = MethodInvocation.class.getName() + " parameter incorrectly constructed.  getMethod() returned null";
+            throw new IllegalArgumentException(msg);
+
+        }
+
+        // look for DenyAll, PermitAll, and AllowedRoles in that order
+        Annotation annotation = method.getAnnotation(DenyAll.class);
+
+        if (annotation == null) {
+            annotation = method.getAnnotation(PermitAll.class);
+        }
+
+        if (annotation == null) {
+            annotation = method.getAnnotation(RolesAllowed.class);
+        }
+
+        // if still null check at the class level
+        Object miThis = methodInvocation.getThis();
+        if (annotation == null && miThis != null) {
+
+            annotation = miThis.getClass().getAnnotation(DenyAll.class);
+
+            if (annotation == null) {
+                annotation = miThis.getClass().getAnnotation(PermitAll.class);
+            }
+
+            if (annotation == null) {
+                annotation = miThis.getClass().getAnnotation(RolesAllowed.class);
+            }
+        }
+
+        return annotation;
+    }
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/aop/PermitAllAnnotationHandler.java b/core/src/main/java/org/apache/shiro/authz/aop/PermitAllAnnotationHandler.java
new file mode 100644
index 0000000..3f8d2b6
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/aop/PermitAllAnnotationHandler.java
@@ -0,0 +1,49 @@
+/*
+ * 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.authz.aop;
+
+import javax.annotation.security.PermitAll;
+import java.lang.annotation.Annotation;
+
+/**
+ * This {@link org.apache.shiro.aop.AnnotationHandler AnnotationHandler} allows access from any subject
+ * (anonymous or logged in user).  It is largely a no-op or for documentation. However, this annotation WILL override
+ * a {@link javax.annotation.security.DenyAll DenyAll} or {@link javax.annotation.security.RolesAllowed RolesAllowed}
+ * annotation if those annotations are placed at the class level and {@link PermitAll} is placed on a method.
+ *
+ * @since 1.5.0
+ */
+public class PermitAllAnnotationHandler extends AuthorizingAnnotationHandler {
+
+
+    /**
+     * Default no-argument constructor that ensures this interceptor looks for a {@link PermitAll}
+     * annotation in a method declaration.
+     */
+    public PermitAllAnnotationHandler() {
+        super(PermitAll.class);
+    }
+
+    /**
+     * No-op, the {@link PermitAll} annotation allows all subjects (including guests/anonymous).
+     *
+     * @param a the annotation to check for one or more roles
+     */
+    public void assertAuthorized(Annotation a) { }
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/aop/RolesAllowedAnnotationHandler.java b/core/src/main/java/org/apache/shiro/authz/aop/RolesAllowedAnnotationHandler.java
new file mode 100644
index 0000000..ec1ebcc
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/aop/RolesAllowedAnnotationHandler.java
@@ -0,0 +1,70 @@
+/*
+ * 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.authz.aop;
+
+import org.apache.shiro.authz.AuthorizationException;
+
+import javax.annotation.security.RolesAllowed;
+import java.lang.annotation.Annotation;
+
+/**
+ * Checks to see if a @{@link RolesAllowed} annotation is declared, and if so, performs
+ * a role check to see if the calling <code>Subject</code> is allowed to proceed.
+ *
+ * @since 1.5.0
+ */
+public class RolesAllowedAnnotationHandler extends AuthorizingAnnotationHandler {
+
+    /**
+     * Default no-argument constructor that ensures this handler looks for
+     * {@link org.apache.shiro.authz.annotation.RequiresRoles RequiresRoles} annotations.
+     */
+    public RolesAllowedAnnotationHandler() {
+        super(RolesAllowed.class);
+    }
+
+    /**
+     * Ensures that the calling <code>Subject</code> has one of the Annotation's specified roles, and if not, throws an
+     * <code>AuthorizingException</code> indicating that access is denied.
+     *
+     * @param a the RolesAllowed annotation to use to check for one or more roles
+     * @throws org.apache.shiro.authz.AuthorizationException
+     *          if the calling <code>Subject</code> does not have the role necessary to
+     *          proceed.
+     */
+    public void assertAuthorized(Annotation a) throws AuthorizationException {
+        if (!(a instanceof RolesAllowed)) return;
+
+        RolesAllowed raAnnotation = (RolesAllowed) a;
+        String[] roles = raAnnotation.value();
+
+        if (roles.length == 1) {
+            getSubject().checkRole(roles[0]);
+            return;
+        }
+
+        // Logical OR
+
+        // Avoid processing exceptions unnecessarily - "delay" throwing the exception by calling hasRole first
+        boolean hasAtLeastOneRole = false;
+        for (String role : roles) if (getSubject().hasRole(role)) hasAtLeastOneRole = true;
+        // Cause the exception if none of the role match, note that the exception message will be a bit misleading
+        if (!hasAtLeastOneRole) getSubject().checkRole(roles[0]);
+    }
+}
\ No newline at end of file
diff --git a/core/src/test/java/org/apache/shiro/authz/aop/DenyAllAnnotationHandlerTest.java b/core/src/test/java/org/apache/shiro/authz/aop/DenyAllAnnotationHandlerTest.java
new file mode 100644
index 0000000..27a4629
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/authz/aop/DenyAllAnnotationHandlerTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.authz.aop;
+
+import org.apache.shiro.authz.UnauthenticatedException;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.test.SecurityManagerTestSupport;
+import org.junit.Test;
+
+import javax.annotation.security.DenyAll;
+import java.lang.annotation.Annotation;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+
+/**
+ * Test cases for the {@link DenyAllAnnotationHandler} class.
+ */
+public class DenyAllAnnotationHandlerTest extends SecurityManagerTestSupport {
+    private Subject subject;
+
+    @Test(expected = UnauthenticatedException.class)
+    public void testGuestSingleRoleAssertion() throws Throwable {
+        DenyAllAnnotationHandler handler = new DenyAllAnnotationHandler();
+
+        Annotation denyAllAnnotation = new DenyAll() {
+            public Class<? extends Annotation> annotationType() {
+                return DenyAll.class;
+            }
+        };
+
+        handler.assertAuthorized(denyAllAnnotation);
+    }
+
+    @Test(expected = UnauthenticatedException.class)
+    public void testOneOfTheRolesRequired() throws Throwable {
+        subject = createMock(Subject.class);
+        expect(subject.hasRole("blah")).andReturn(true);
+        replay(subject);
+        DenyAllAnnotationHandler handler = new DenyAllAnnotationHandler() {
+            @Override
+            protected Subject getSubject() {
+                return subject;
+            }
+        };
+
+        Annotation denyAllAnnotation = new DenyAll() {
+
+            public Class<? extends Annotation> annotationType() {
+                return DenyAll.class;
+            }
+        };
+        handler.assertAuthorized(denyAllAnnotation);
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/authz/aop/Jsr250AnnotationHandlerInteractionTest.java b/core/src/test/java/org/apache/shiro/authz/aop/Jsr250AnnotationHandlerInteractionTest.java
new file mode 100644
index 0000000..ae80ea9
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/authz/aop/Jsr250AnnotationHandlerInteractionTest.java
@@ -0,0 +1,183 @@
+package org.apache.shiro.authz.aop;
+
+import org.apache.shiro.aop.MethodInvocation;
+import org.apache.shiro.authz.UnauthenticatedException;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.test.SecurityManagerTestSupport;
+import org.apache.shiro.util.ThreadContext;
+import org.easymock.EasyMock;
+import org.junit.Test;
+
+import javax.annotation.security.DenyAll;
+import javax.annotation.security.PermitAll;
+import javax.annotation.security.RolesAllowed;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+
+import static org.easymock.EasyMock.anyString;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+
+public class Jsr250AnnotationHandlerInteractionTest extends SecurityManagerTestSupport {
+
+    @Test(expected = UnauthenticatedException.class)
+    public void denyClassNoAnnotation() throws Throwable {
+        createSubject();
+        invokeWithInterceptor(new ExampleDenyAtClass(), "expectDenied");
+    }
+
+    @Test
+    public void denyClassPermitMethod() throws Throwable {
+        createSubject();
+        invokeWithInterceptor(new ExampleDenyAtClass(), "expectAllowed");
+    }
+
+    @Test
+    public void denyClassRolesAllowedAnnotation() throws Throwable {
+        createSubject("foo");
+        invokeWithInterceptor(new ExampleDenyAtClass(), "withRoles");
+    }
+
+    @Test(expected = UnauthenticatedException.class)
+    public void denyClassRolesAllowedAnnotation_noRoles() throws Throwable {
+        createSubject();
+        invokeWithInterceptor(new ExampleDenyAtClass(), "withRoles");
+    }
+
+    @Test(expected = UnauthenticatedException.class)
+    public void permitClassNoAnnotation() throws Throwable {
+        createSubject();
+        invokeWithInterceptor(new ExamplePermitAtClass(), "expectDenied");
+    }
+
+    @Test
+    public void permitClassPermitMethod() throws Throwable {
+        createSubject();
+        invokeWithInterceptor(new ExamplePermitAtClass(), "expectAllowed");
+    }
+
+    @Test
+    public void permitClassRolesAllowedAnnotation() throws Throwable {
+        createSubject("foo");
+        invokeWithInterceptor(new ExamplePermitAtClass(), "withRoles");
+    }
+
+    @Test(expected = UnauthenticatedException.class)
+    public void permitClassRolesAllowedAnnotation_noRoles() throws Throwable {
+        createSubject();
+        invokeWithInterceptor(new ExamplePermitAtClass(), "withRoles");
+    }
+
+    @Test(expected = UnauthenticatedException.class)
+    public void rolesAllowedNoAnnotation() throws Throwable {
+        createSubject();
+        invokeWithInterceptor(new ExampleRolesAllowedAtClass(), "expectDenied");
+    }
+
+    @Test
+    public void rolesAllowedPermitMethod() throws Throwable {
+        createSubject();
+        invokeWithInterceptor(new ExampleRolesAllowedAtClass(), "expectAllowed");
+    }
+
+    @Test
+    public void rolesAllowedRolesAllowedAnnotation() throws Throwable {
+        createSubject("foo");
+        invokeWithInterceptor(new ExampleRolesAllowedAtClass(), "withRoles");
+    }
+
+    @Test(expected = UnauthenticatedException.class)
+    public void rolesAllowedRolesAllowedAnnotation_noRoles() throws Throwable {
+        createSubject();
+        invokeWithInterceptor(new ExampleRolesAllowedAtClass(), "withRoles");
+    }
+
+    private void invokeWithInterceptor(Object target, String methodName) throws Throwable {
+        MethodInvocation mi = new StubMethodInvocation(target.getClass().getDeclaredMethod(methodName), target);
+        new Jsr250MethodInterceptor().invoke(mi);
+    }
+
+    private Subject createSubject(String... roles) {
+        Subject subject = createMock(Subject.class);
+        Arrays.stream(roles).forEach(role -> {
+            expect(subject.hasRole(role)).andReturn(true).anyTimes();
+            subject.checkRole(role);
+            EasyMock.expectLastCall().anyTimes();
+        });
+        expect(subject.hasRole(anyString())).andReturn(false).anyTimes();
+        subject.checkRole(anyString());
+        EasyMock.expectLastCall().andThrow(new UnauthenticatedException("Test thrown authz exception")).anyTimes();
+        replay(subject);
+        ThreadContext.bind(subject);
+        return subject;
+    }
+
+    @DenyAll
+    static class ExampleDenyAtClass {
+
+        void expectDenied() {}
+
+        @PermitAll
+        void expectAllowed() {}
+
+        @RolesAllowed({"blah2", "foo"})
+        void withRoles() {}
+    }
+
+    @PermitAll
+    static class ExamplePermitAtClass {
+
+        @DenyAll
+        void expectDenied() {}
+
+        void expectAllowed() {}
+
+        @RolesAllowed({"blah2", "foo"})
+        void withRoles() {}
+    }
+
+    @RolesAllowed({"blah2", "foo"})
+    static class ExampleRolesAllowedAtClass {
+
+        @DenyAll
+        void expectDenied() {}
+
+        @PermitAll
+        void expectAllowed() {}
+
+        void withRoles() {}
+    }
+
+    static class StubMethodInvocation implements MethodInvocation {
+
+        private final Method method;
+        private final Object target;
+
+        StubMethodInvocation(Method method, Object target) {
+            this.method = method;
+            this.target = target;
+        }
+
+        @Override
+        public Object proceed() throws Throwable {
+            return getMethod();
+        }
+
+        @Override
+        public Method getMethod() {
+            return method;
+        }
+
+        @Override
+        public Object[] getArguments() {
+            // returning params here instead of the actual objects
+            return method.getParameters();
+        }
+
+        @Override
+        public Object getThis() {
+            return target;
+        }
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/authz/aop/PermitAllAnnotationHandlerTest.java b/core/src/test/java/org/apache/shiro/authz/aop/PermitAllAnnotationHandlerTest.java
new file mode 100644
index 0000000..219f58b
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/authz/aop/PermitAllAnnotationHandlerTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.authz.aop;
+
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.test.SecurityManagerTestSupport;
+import org.junit.Test;
+
+import javax.annotation.security.PermitAll;
+import java.lang.annotation.Annotation;
+
+/**
+ * Test cases for the {@link PermitAllAnnotationHandler} class.
+ */
+public class PermitAllAnnotationHandlerTest extends SecurityManagerTestSupport {
+    private Subject subject;
+
+    @Test
+    public void testPermitAll() throws Throwable {
+        PermitAllAnnotationHandler handler = new PermitAllAnnotationHandler();
+
+        Annotation permitallAnnotation = new PermitAll() {
+            public Class<? extends Annotation> annotationType() {
+                return PermitAll.class;
+            }
+        };
+
+        handler.assertAuthorized(permitallAnnotation);
+    }
+}
diff --git a/core/src/test/java/org/apache/shiro/authz/aop/RolesAllowedAnnotationHandlerTest.java b/core/src/test/java/org/apache/shiro/authz/aop/RolesAllowedAnnotationHandlerTest.java
new file mode 100644
index 0000000..ea9678e
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/authz/aop/RolesAllowedAnnotationHandlerTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.authz.aop;
+
+import org.apache.shiro.authz.UnauthenticatedException;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.test.SecurityManagerTestSupport;
+import org.junit.Test;
+
+import javax.annotation.security.RolesAllowed;
+import java.lang.annotation.Annotation;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+
+/**
+ * Test cases for the {@link RolesAllowedAnnotationHandler} class.
+ */
+public class RolesAllowedAnnotationHandlerTest extends SecurityManagerTestSupport {
+    private Subject subject;
+
+    @Test(expected = UnauthenticatedException.class)
+    public void testGuestSingleRoleAssertion() throws Throwable {
+        RolesAllowedAnnotationHandler handler = new RolesAllowedAnnotationHandler();
+
+        Annotation rolesAllowedAnnotation = new RolesAllowed() {
+            public String[] value() {
+                return new String[]{"blah"};
+            }
+
+            public Class<? extends Annotation> annotationType() {
+                return RolesAllowed.class;
+            }
+        };
+
+        handler.assertAuthorized(rolesAllowedAnnotation);
+    }
+
+    @Test(expected = UnauthenticatedException.class)
+    public void testGuestMultipleRolesAssertion() throws Throwable {
+        RolesAllowedAnnotationHandler handler = new RolesAllowedAnnotationHandler();
+
+        Annotation rolesAllowedAnnotation = new RolesAllowed() {
+            public String[] value() {
+                return new String[]{"blah", "blah2"};
+            }
+
+            public Class<? extends Annotation> annotationType() {
+                return RolesAllowed.class;
+            }
+        };
+
+        handler.assertAuthorized(rolesAllowedAnnotation);
+    }
+
+    @Test
+    public void testOneOfTheRolesRequired() throws Throwable {
+        subject = createMock(Subject.class);
+        expect(subject.hasRole("blah")).andReturn(true);
+        expect(subject.hasRole("blah2")).andReturn(false);
+        replay(subject);
+        RolesAllowedAnnotationHandler handler = new RolesAllowedAnnotationHandler() {
+            @Override
+            protected Subject getSubject() {
+                return subject;
+            }
+        };
+
+        Annotation rolesAllowedAnnotation = new RolesAllowed() {
+            public String[] value() {
+                return new String[]{"blah", "blah2"};
+            }
+
+            public Class<? extends Annotation> annotationType() {
+                return RolesAllowed.class;
+            }
+        };
+        handler.assertAuthorized(rolesAllowedAnnotation);
+    }
+}