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:27 UTC

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

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