Adds support for javax.annotation.security.RolesAllowed, PermitAll, and DenyAll
diff --git a/core/pom.xml b/core/pom.xml
index 39ea570..17b695f 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -94,6 +94,12 @@
<artifactId>shiro-event</artifactId>
</dependency>
+ <dependency>
+ <groupId>javax.annotation</groupId>
+ <artifactId>javax.annotation-api</artifactId>
+ <optional>true</optional>
+ </dependency>
+
<!-- Test dependencies -->
<dependency>
<groupId>org.slf4j</groupId>
diff --git a/core/src/main/java/org/apache/shiro/authz/aop/JavaxSecurityRolesAnnotationHandler.java b/core/src/main/java/org/apache/shiro/authz/aop/JavaxSecurityRolesAnnotationHandler.java
new file mode 100644
index 0000000..6198deb
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/aop/JavaxSecurityRolesAnnotationHandler.java
@@ -0,0 +1,84 @@
+/*
+ * 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 org.apache.shiro.authz.annotation.Logical;
+import org.apache.shiro.authz.annotation.RequiresRoles;
+
+import javax.annotation.security.RolesAllowed;
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+
+/**
+ * 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.4.0
+ */
+public class JavaxSecurityRolesAnnotationHandler extends AuthorizingAnnotationHandler {
+
+ /**
+ * Default no-argument constructor that ensures this handler looks for
+ * {@link RequiresRoles RequiresRoles} annotations.
+ */
+ public JavaxSecurityRolesAnnotationHandler() {
+ super(RolesAllowed.class);
+ }
+
+ /**
+ * Ensures that the calling <code>Subject</code> has 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 AuthorizationException
+ * if the calling <code>Subject</code> does not have the role(s) necessary to
+ * proceed.
+ */
+ public void assertAuthorized(Annotation a) throws AuthorizationException {
+
+ if (!(a instanceof RolesAllowed)) {
+ return;
+ }
+
+ RolesAllowed rannotation = (RolesAllowed) a;
+ String[] roles = rannotation.value();
+
+ if (roles.length == 1) {
+ getSubject().checkRole(roles[0]);
+ return;
+ }
+
+ // 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]);
+ }
+ }
+
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/aop/JavaxSecurityRolesMethodInterceptor.java b/core/src/main/java/org/apache/shiro/authz/aop/JavaxSecurityRolesMethodInterceptor.java
new file mode 100644
index 0000000..8530fab
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/aop/JavaxSecurityRolesMethodInterceptor.java
@@ -0,0 +1,84 @@
+/*
+ * 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.AnnotationResolver;
+import org.apache.shiro.aop.MethodInvocation;
+import org.apache.shiro.authz.AuthorizationException;
+import org.apache.shiro.authz.UnauthorizedException;
+
+import javax.annotation.security.DenyAll;
+import javax.annotation.security.PermitAll;
+import javax.annotation.security.RolesAllowed;
+
+
+/**
+ * Checks to see if a @{@link javax.annotation.security.RolesAllowed RolesAllowed} annotation is declared, and if so, performs
+ * a role check to see if the calling <code>Subject</code> is allowed to invoke the method.
+ *
+ * @since 1.4.0
+ */
+public class JavaxSecurityRolesMethodInterceptor extends AuthorizingAnnotationMethodInterceptor {
+
+ /**
+ * Default no-argument constructor that ensures this interceptor looks for
+ * {@link javax.annotation.security.RolesAllowed RolesAllowed} annotations in a method declaration.
+ */
+ public JavaxSecurityRolesMethodInterceptor() {
+ super( new JavaxSecurityRolesAnnotationHandler() );
+ }
+
+ /**
+ * @param resolver
+ * @since 1.4.0
+ */
+ public JavaxSecurityRolesMethodInterceptor(AnnotationResolver resolver) {
+ super(new JavaxSecurityRolesAnnotationHandler(), resolver);
+ }
+
+ @Override
+ public void assertAuthorized(MethodInvocation mi) throws AuthorizationException {
+
+ DenyAll denyAllOnMethod = mi.getMethod().getAnnotation(DenyAll.class); // this can only be at the method level
+ PermitAll permitAllOnMethod = mi.getMethod().getAnnotation(PermitAll.class); // a PermitAll on a class, is the default condition.
+ RolesAllowed rolesAllowedOnMethod = mi.getMethod().getAnnotation(RolesAllowed.class);
+
+ // DenyAll on the method take precedence over RolesAllowed and PermitAll
+ if (denyAllOnMethod != null ) {
+ throw new UnauthorizedException("Subject does not have access to method due to DenyAll annotation.");
+ }
+
+ // RolesAllowed on the method takes precedence over PermitAll
+ if(rolesAllowedOnMethod != null) {
+ super.assertAuthorized(mi);
+ return;
+ }
+
+ // PermitAll on method takes precedence over RolesAllowed on the class
+ if (permitAllOnMethod != null) {
+ // just return
+ return;
+ }
+
+ // DenyAll can't be attached to classes
+
+ // RolesAllowed on the class takes precedence over PermitAll
+ super.assertAuthorized(mi);
+ }
+}
diff --git a/core/src/test/groovy/org/apache/shiro/aop/JavaxSecurityRolesMethodIntercepterTest.groovy b/core/src/test/groovy/org/apache/shiro/aop/JavaxSecurityRolesMethodIntercepterTest.groovy
new file mode 100644
index 0000000..9828ee6
--- /dev/null
+++ b/core/src/test/groovy/org/apache/shiro/aop/JavaxSecurityRolesMethodIntercepterTest.groovy
@@ -0,0 +1,175 @@
+/*
+ * 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.aop
+
+import org.apache.shiro.authz.UnauthorizedException
+import org.apache.shiro.authz.aop.JavaxSecurityRolesMethodInterceptor
+import org.apache.shiro.subject.Subject
+import org.apache.shiro.util.ThreadContext
+import org.junit.Assert;
+import org.junit.Test;
+
+import static org.apache.shiro.aop.JavaxSecurityRoleStubs.*
+
+import java.lang.reflect.Method
+
+import static org.easymock.EasyMock.*
+import static org.junit.Assert.*
+
+
+/**
+ * Tests for {@link JavaxSecurityRolesMethodInterceptor}.
+ */
+public class JavaxSecurityRolesMethodIntercepterTest {
+
+ @Test
+ public void rolesAllowedOnMethod() throws Throwable {
+
+ Subject subject = mock(Subject)
+ expect(subject.checkRole("RoleOne"))
+ assertTrue executeStubMethod(new RolesAllowedOnMethod(), subject);
+ }
+
+ @Test
+ public void rolesAllowedOnClass() throws Throwable {
+
+ Subject subject = mock(Subject)
+ expect(subject.checkRole("RoleOne"))
+ assertTrue executeStubMethod(new RolesAllowedOnClass(), subject);
+ }
+
+
+ @Test
+ public void permitAllOnMethod() throws Throwable {
+
+ Subject subject = mock(Subject)
+ assertTrue executeStubMethod(new PermitAllOnMethod(), subject);
+ }
+
+ @Test
+ public void permitAllOnClass() throws Throwable {
+
+ Subject subject = mock(Subject)
+ assertTrue executeStubMethod(new PermitAllOnClass(), subject);
+ }
+
+ @Test(expected = UnauthorizedException.class)
+ public void denyAllOnMethod() throws Throwable {
+
+ Subject subject = mock(Subject)
+ executeStubMethod(new DenyAllOnMethod(), subject);
+ }
+
+ @Test(expected = UnauthorizedException.class)
+ public void rolesAllowedOnClassDenyAllOnMethod() throws Throwable {
+
+ Subject subject = mock(Subject)
+ executeStubMethod(new RolesAllowedOnClassDenyAllOnMethod(), subject);
+ }
+
+ @Test(expected = UnauthorizedException.class)
+ public void rolesAllowedOnMethodDenyAllOnMethod() throws Throwable {
+
+ Subject subject = mock(Subject)
+ executeStubMethod(new RolesAllowedOnMethodDenyAllOnMethod(), subject);
+ }
+
+ @Test
+ public void rolesAllowedOnClassPermitAllOnClass() throws Throwable {
+
+ Subject subject = mock(Subject)
+ expect(subject.checkRole("RoleOne"))
+ assertTrue executeStubMethod(new RolesAllowedOnClassPermitAllOnClass(), subject);
+ }
+
+ @Test
+ public void rolesAllowedOnMethodPermitAllOnMethod() throws Throwable {
+
+ Subject subject = mock(Subject)
+ expect(subject.checkRole("RoleOne"))
+ assertTrue executeStubMethod(new RolesAllowedOnMethodPermitAllOnMethod(), subject);
+ }
+
+ @Test
+ public void rolesAllowedOnClassPermitAllOnMethod() throws Throwable {
+
+ Subject subject = mock(Subject)
+ assertTrue executeStubMethod(new RolesAllowedOnClassPermitAllOnMethod(), subject);
+ }
+
+ @Test
+ public void permitAllOnClassRolesAllowedOnMethod() throws Throwable {
+
+ Subject subject = mock(Subject)
+ expect(subject.checkRole("RoleOne"))
+ assertTrue executeStubMethod(new PermitAllOnClassRolesAllowedOnMethod(), subject);
+ }
+
+
+
+
+
+
+ static boolean executeStubMethod(final SimpleStub stub, Subject subject) {
+
+ try {
+ replay subject
+ ThreadContext.bind(subject)
+
+ JavaxSecurityRolesMethodInterceptor interceptor = new JavaxSecurityRolesMethodInterceptor();
+
+ Object result = interceptor.invoke(new MethodInvocation() {
+ @Override
+ public Object proceed() throws Throwable {
+ return stub.callMe();
+ }
+
+ @Override
+ public Method getMethod() {
+ try {
+ return stub.getClass().getMethod("callMe");
+ } catch (NoSuchMethodException e) {
+ Assert.fail("Failed to return method 'callMe()' on stub");
+ return null;
+ }
+ }
+
+ @Override
+ public Object[] getArguments() {
+ return new Object[0];
+ }
+
+ @Override
+ public Object getThis() {
+ return stub;
+ }
+ });
+
+ verify subject
+
+ return result;
+ }
+ finally {
+ ThreadContext.unbindSubject()
+ }
+
+
+
+ }
+}
diff --git a/core/src/test/java/org/apache/shiro/aop/JavaxSecurityRoleStubs.java b/core/src/test/java/org/apache/shiro/aop/JavaxSecurityRoleStubs.java
new file mode 100644
index 0000000..2a07a2d
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/aop/JavaxSecurityRoleStubs.java
@@ -0,0 +1,114 @@
+/*
+ * 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.aop;
+
+import javax.annotation.security.DenyAll;
+import javax.annotation.security.PermitAll;
+import javax.annotation.security.RolesAllowed;
+
+public final class JavaxSecurityRoleStubs {
+
+ public interface SimpleStub {
+ boolean callMe();
+ }
+
+
+ @RolesAllowed("RoleOne")
+ public static class RolesAllowedOnClass implements SimpleStub {
+ public boolean callMe() {
+ return true;
+ }
+ }
+
+ public static class RolesAllowedOnMethod implements SimpleStub {
+ @RolesAllowed("RoleOne")
+ public boolean callMe() {
+ return true;
+ }
+ }
+
+ public static class PermitAllOnMethod implements SimpleStub {
+ @PermitAll
+ public boolean callMe() {
+ return true;
+ }
+ }
+
+ @PermitAll
+ public static class PermitAllOnClass implements SimpleStub {
+ public boolean callMe() {
+ return true;
+ }
+ }
+
+ public static class DenyAllOnMethod implements SimpleStub {
+ @DenyAll
+ public boolean callMe() {
+ return false;
+ }
+ }
+
+ @RolesAllowed("RoleOne")
+ public static class RolesAllowedOnClassDenyAllOnMethod implements SimpleStub {
+ @DenyAll
+ public boolean callMe() {
+ return false;
+ }
+ }
+
+ public static class RolesAllowedOnMethodDenyAllOnMethod implements SimpleStub {
+ @RolesAllowed("RoleOne")
+ @DenyAll
+ public boolean callMe() {
+ return false;
+ }
+ }
+
+ @RolesAllowed("RoleOne")
+ @PermitAll
+ public static class RolesAllowedOnClassPermitAllOnClass implements SimpleStub {
+ public boolean callMe() {
+ return true;
+ }
+ }
+
+ public static class RolesAllowedOnMethodPermitAllOnMethod implements SimpleStub {
+ @RolesAllowed("RoleOne")
+ @PermitAll
+ public boolean callMe() {
+ return true;
+ }
+ }
+
+ @RolesAllowed("RoleOne")
+ public static class RolesAllowedOnClassPermitAllOnMethod implements SimpleStub {
+ @PermitAll
+ public boolean callMe() {
+ return true;
+ }
+ }
+
+ @PermitAll
+ public static class PermitAllOnClassRolesAllowedOnMethod implements SimpleStub {
+ @RolesAllowed("RoleOne")
+ public boolean callMe() {
+ return true;
+ }
+ }
+}
diff --git a/pom.xml b/pom.xml
index 1162d41..3959dc8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -813,6 +813,11 @@
<scope>provided</scope>
</dependency>
<dependency>
+ <groupId>javax.annotation</groupId>
+ <artifactId>javax.annotation-api</artifactId>
+ <version>1.2</version>
+ </dependency>
+ <dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
diff --git a/samples/jaxrs/pom.xml b/samples/jaxrs/pom.xml
index da02fed..3c57d32 100644
--- a/samples/jaxrs/pom.xml
+++ b/samples/jaxrs/pom.xml
@@ -80,6 +80,12 @@
<artifactId>shiro-jaxrs</artifactId>
</dependency>
+ <!-- enable optional support for javax.annotation.security annotations -->
+ <dependency>
+ <groupId>javax.annotation</groupId>
+ <artifactId>javax.annotation-api</artifactId>
+ </dependency>
+
<dependency>
<!-- Required for any libraries that expect to call the commons logging APIs -->
<groupId>org.slf4j</groupId>
diff --git a/samples/jaxrs/src/main/java/org/apache/shiro/samples/jaxrs/SampleApplication.java b/samples/jaxrs/src/main/java/org/apache/shiro/samples/jaxrs/SampleApplication.java
index b7ae949..00b8649 100644
--- a/samples/jaxrs/src/main/java/org/apache/shiro/samples/jaxrs/SampleApplication.java
+++ b/samples/jaxrs/src/main/java/org/apache/shiro/samples/jaxrs/SampleApplication.java
@@ -20,6 +20,7 @@
import org.apache.shiro.samples.jaxrs.resources.HelloResource;
import org.apache.shiro.samples.jaxrs.resources.SecureResource;
+import org.apache.shiro.web.jaxrs.JavaxSecurityAnnotationFilterFeature;
import org.apache.shiro.web.jaxrs.ShiroFeature;
import javax.ws.rs.ApplicationPath;
@@ -41,6 +42,9 @@
// register Shiro
classes.add(ShiroFeature.class);
+ // optional javax.annotations.security support
+ classes.add(JavaxSecurityAnnotationFilterFeature.class);
+
// register resources
classes.add(HelloResource.class);
classes.add(SecureResource.class);
diff --git a/samples/jaxrs/src/main/java/org/apache/shiro/samples/jaxrs/resources/SecureResource.java b/samples/jaxrs/src/main/java/org/apache/shiro/samples/jaxrs/resources/SecureResource.java
index c590987..47e347e 100644
--- a/samples/jaxrs/src/main/java/org/apache/shiro/samples/jaxrs/resources/SecureResource.java
+++ b/samples/jaxrs/src/main/java/org/apache/shiro/samples/jaxrs/resources/SecureResource.java
@@ -25,12 +25,15 @@
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.authz.annotation.RequiresUser;
+import javax.annotation.security.RolesAllowed;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
+import static org.glassfish.grizzly.http.util.Header.Allow;
+
@Path("secure")
@Produces({"application/json","plain/text"})
public class SecureResource {
@@ -71,5 +74,11 @@
return "protected";
}
+ @RolesAllowed("admin")
+ @Path("RolesAllowed")
+ @GET
+ public String protectedByRolesAllowed() {
+ return "protected";
+ }
}
diff --git a/samples/jaxrs/src/test/groovy/org/apache/shiro/web/jaxrs/ContainerIntegrationIT.groovy b/samples/jaxrs/src/test/groovy/org/apache/shiro/web/jaxrs/ContainerIntegrationIT.groovy
index 84899d7..40495ce 100644
--- a/samples/jaxrs/src/test/groovy/org/apache/shiro/web/jaxrs/ContainerIntegrationIT.groovy
+++ b/samples/jaxrs/src/test/groovy/org/apache/shiro/web/jaxrs/ContainerIntegrationIT.groovy
@@ -96,6 +96,31 @@
}
@Test
+ void testSecuredRolesAllowed() {
+
+ get(getBaseUri() + "secure/RolesAllowed")
+ .then()
+ .assertThat().statusCode(is(401))
+
+ given()
+ .header("Authorization", getBasicAuthorizationHeaderValue("guest", "guest"))
+ .when()
+ .get(getBaseUri() + "secure/RolesAllowed")
+ .then()
+ .assertThat()
+ .statusCode(is(403)).and()
+
+ given()
+ .header("Authorization", getBasicAuthorizationHeaderValue("root", "secret"))
+ .when()
+ .get(getBaseUri() + "secure/RolesAllowed")
+ .then()
+ .assertThat()
+ .statusCode(is(200)).and()
+ .body(equalTo("protected"))
+ }
+
+ @Test
void testSecuredRequiresPermissions() {
get(getBaseUri() + "secure/RequiresPermissions")
diff --git a/support/jaxrs/pom.xml b/support/jaxrs/pom.xml
index 05b3c5b..bcf922b 100644
--- a/support/jaxrs/pom.xml
+++ b/support/jaxrs/pom.xml
@@ -49,6 +49,12 @@
<version>2.0.1</version>
</dependency>
+ <dependency>
+ <groupId>javax.annotation</groupId>
+ <artifactId>javax.annotation-api</artifactId>
+ <optional>true</optional>
+ </dependency>
+
<!-- Test Deps -->
<dependency>
<groupId>org.slf4j</groupId>
diff --git a/support/jaxrs/src/main/java/org/apache/shiro/web/jaxrs/JavaxSecurityAnnotationFilterFeature.java b/support/jaxrs/src/main/java/org/apache/shiro/web/jaxrs/JavaxSecurityAnnotationFilterFeature.java
new file mode 100644
index 0000000..49d4374
--- /dev/null
+++ b/support/jaxrs/src/main/java/org/apache/shiro/web/jaxrs/JavaxSecurityAnnotationFilterFeature.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.web.jaxrs;
+
+import org.apache.shiro.web.filter.authz.AuthorizationFilter;
+
+import javax.annotation.security.DenyAll;
+import javax.annotation.security.PermitAll;
+import javax.annotation.security.RolesAllowed;
+import javax.ws.rs.Priorities;
+import javax.ws.rs.container.DynamicFeature;
+import javax.ws.rs.container.ResourceInfo;
+import javax.ws.rs.core.FeatureContext;
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Wraps {@link AuthorizationFilter filters} around JAX-RS resources that are annotated with Shiro annotations.
+ * @since 1.4
+ */
+public class JavaxSecurityAnnotationFilterFeature implements DynamicFeature {
+
+ private static List<Class<? extends Annotation>> javaxSecurityAnnotations = Collections.unmodifiableList(Arrays.asList(
+ DenyAll.class,
+ PermitAll.class,
+ RolesAllowed.class));
+
+ @Override
+ public void configure(ResourceInfo resourceInfo, FeatureContext context) {
+
+ List<Annotation> authzSpecs = new ArrayList<Annotation>();
+
+ for (Class<? extends Annotation> annotationClass : javaxSecurityAnnotations) {
+ // XXX What is the performance of getAnnotation vs getAnnotations?
+ Annotation classAuthzSpec = resourceInfo.getResourceClass().getAnnotation(annotationClass);
+ Annotation methodAuthzSpec = resourceInfo.getResourceMethod().getAnnotation(annotationClass);
+
+ if (classAuthzSpec != null) {
+ authzSpecs.add(classAuthzSpec);
+ }
+ if (methodAuthzSpec != null) {
+ authzSpecs.add(methodAuthzSpec);
+ }
+ }
+
+ if (!authzSpecs.isEmpty()) {
+ context.register(new JavaxSecurityAuthorizationFilter(authzSpecs), Priorities.AUTHORIZATION);
+ }
+ }
+
+}
diff --git a/support/jaxrs/src/main/java/org/apache/shiro/web/jaxrs/JavaxSecurityAuthorizationFilter.java b/support/jaxrs/src/main/java/org/apache/shiro/web/jaxrs/JavaxSecurityAuthorizationFilter.java
new file mode 100644
index 0000000..1519ab5
--- /dev/null
+++ b/support/jaxrs/src/main/java/org/apache/shiro/web/jaxrs/JavaxSecurityAuthorizationFilter.java
@@ -0,0 +1,75 @@
+/*
+ * 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.web.jaxrs;
+
+import org.apache.shiro.authz.aop.AuthorizingAnnotationHandler;
+import org.apache.shiro.authz.aop.JavaxSecurityRolesAnnotationHandler;
+
+import javax.annotation.security.DenyAll;
+import javax.annotation.security.PermitAll;
+import javax.annotation.security.RolesAllowed;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * A filter that grants or denies access to a JAX-RS resource based on the <code>javax.annotation.security</code> annotations on it.
+ *
+ * @see org.apache.shiro.authz.annotation
+ * @since 1.4
+ */
+public class JavaxSecurityAuthorizationFilter implements ContainerRequestFilter {
+
+ private final Map<AuthorizingAnnotationHandler, Annotation> authzChecks;
+
+ public JavaxSecurityAuthorizationFilter(Collection<Annotation> authzSpecs) {
+ Map<AuthorizingAnnotationHandler, Annotation> authChecks = new HashMap<AuthorizingAnnotationHandler, Annotation>(authzSpecs.size());
+ for (Annotation authSpec : authzSpecs) {
+ authChecks.put(createHandler(authSpec), authSpec);
+ }
+ this.authzChecks = Collections.unmodifiableMap(authChecks);
+ }
+
+ private static AuthorizingAnnotationHandler createHandler(Annotation annotation) {
+ Class<?> t = annotation.annotationType();
+
+ if (RolesAllowed.class.equals(t) || DenyAll.class.equals(t) || PermitAll.class.equals(t)) {
+ return new JavaxSecurityRolesAnnotationHandler();
+ }
+
+ else throw new IllegalArgumentException("Cannot create a handler for the unknown for annotation " + t);
+ }
+
+ @Override
+ public void filter(ContainerRequestContext requestContext) throws IOException {
+
+ for (Map.Entry<AuthorizingAnnotationHandler, Annotation> authzCheck : authzChecks.entrySet()) {
+ AuthorizingAnnotationHandler handler = authzCheck.getKey();
+ Annotation authzSpec = authzCheck.getValue();
+ handler.assertAuthorized(authzSpec);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/support/jaxrs/src/test/groovy/org/apache/shiro/web/jaxrs/JavaxSecurityAnnotationFilterFeatureTest.groovy b/support/jaxrs/src/test/groovy/org/apache/shiro/web/jaxrs/JavaxSecurityAnnotationFilterFeatureTest.groovy
new file mode 100644
index 0000000..d43d25e
--- /dev/null
+++ b/support/jaxrs/src/test/groovy/org/apache/shiro/web/jaxrs/JavaxSecurityAnnotationFilterFeatureTest.groovy
@@ -0,0 +1,55 @@
+/*
+ * 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.web.jaxrs
+
+import org.easymock.Capture
+import org.junit.Test
+
+import javax.annotation.security.DenyAll
+import javax.annotation.security.PermitAll
+import javax.annotation.security.RolesAllowed
+import javax.ws.rs.Priorities
+import javax.ws.rs.container.ResourceInfo
+import javax.ws.rs.core.FeatureContext
+
+import static org.easymock.EasyMock.*
+
+/**
+ * Tests for {@link JavaxSecurityAnnotationFilterFeature}.
+ * @since 1.4.0
+ */
+class JavaxSecurityAnnotationFilterFeatureTest {
+
+ @Test
+ void simpleTest() {
+
+ def resourceInfo = mock(ResourceInfo)
+ def featureContext = mock(FeatureContext)
+ def filterCapture = new Capture<JavaxSecurityAuthorizationFilter>()
+ expect(resourceInfo.getResourceClass()).andReturn(JavaxAnnotatedStub).anyTimes()
+ expect(resourceInfo.getResourceMethod()).andReturn(JavaxAnnotatedStub.getMethod("denyMe")).anyTimes()
+ expect(featureContext.register(capture(filterCapture), eq(Priorities.AUTHORIZATION))).andReturn(featureContext)
+
+ replay resourceInfo, featureContext
+
+ new JavaxSecurityAnnotationFilterFeature().configure(resourceInfo, featureContext)
+
+ verify resourceInfo, featureContext
+ }
+}
diff --git a/support/jaxrs/src/test/groovy/org/apache/shiro/web/jaxrs/JavaxSecurityAuthorizationFilterTest.groovy b/support/jaxrs/src/test/groovy/org/apache/shiro/web/jaxrs/JavaxSecurityAuthorizationFilterTest.groovy
new file mode 100644
index 0000000..0d1e711
--- /dev/null
+++ b/support/jaxrs/src/test/groovy/org/apache/shiro/web/jaxrs/JavaxSecurityAuthorizationFilterTest.groovy
@@ -0,0 +1,92 @@
+/*
+ * 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.web.jaxrs
+
+import org.apache.shiro.authz.annotation.RequiresGuest
+import org.apache.shiro.subject.Subject
+import org.apache.shiro.util.ThreadContext
+import org.junit.Assert
+import org.junit.Test
+
+import javax.annotation.security.PermitAll
+import javax.annotation.security.RolesAllowed
+import javax.ws.rs.container.ContainerRequestContext
+import java.lang.annotation.Annotation
+
+import static org.easymock.EasyMock.*
+import static org.hamcrest.Matchers.*
+import static org.hamcrest.MatcherAssert.*
+
+/**
+ * Tests for {@link JavaxSecurityAuthorizationFilter}.
+ * @since 1.4.0
+ */
+class JavaxSecurityAuthorizationFilterTest {
+
+ @Test
+ void simpleTest() {
+
+ List<Annotation> annotationSpecs = new ArrayList<Annotation>();
+ annotationSpecs.add(JavaxAnnotatedStub.getAnnotation(PermitAll.class))
+ annotationSpecs.add(JavaxAnnotatedStub.getMethod("rolesAllowed").getAnnotation(RolesAllowed.class))
+
+ def requestContext = mock(ContainerRequestContext)
+ def subject = mock(Subject)
+ subject.checkRole("Role1")
+
+ try {
+ ThreadContext.bind(subject)
+
+ replay requestContext, subject
+
+ new JavaxSecurityAuthorizationFilter(annotationSpecs).filter(requestContext)
+
+ verify requestContext, subject
+ }
+ finally {
+ ThreadContext.unbindSubject()
+ }
+ }
+
+ @Test
+ void unhandledAnnotationTest() {
+ List<Annotation> annotationSpecs = new ArrayList<Annotation>();
+ annotationSpecs.add(JavaxAnnotatedStub.getMethod("requiresGuest").getAnnotation(RequiresGuest.class))
+
+ def requestContext = mock(ContainerRequestContext)
+ def subject = mock(Subject)
+
+ try {
+ ThreadContext.bind(subject)
+
+ replay requestContext, subject
+
+ new JavaxSecurityAuthorizationFilter(annotationSpecs).filter(requestContext)
+ Assert.fail("expected IllegalArgumentException")
+ }
+ catch(IllegalArgumentException e){
+ assertThat e.getMessage(), stringContainsInOrder(RequiresGuest.getName())
+ }
+ finally {
+
+ verify requestContext, subject
+ ThreadContext.unbindSubject()
+ }
+ }
+}
diff --git a/support/jaxrs/src/test/java/org/apache.shiro.web.jaxrs/JavaxAnnotatedStub.java b/support/jaxrs/src/test/java/org/apache.shiro.web.jaxrs/JavaxAnnotatedStub.java
new file mode 100644
index 0000000..eec850b
--- /dev/null
+++ b/support/jaxrs/src/test/java/org/apache.shiro.web.jaxrs/JavaxAnnotatedStub.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.web.jaxrs;
+
+import org.apache.shiro.authz.annotation.RequiresGuest;
+
+import javax.annotation.security.DenyAll;
+import javax.annotation.security.PermitAll;
+import javax.annotation.security.RolesAllowed;
+
+@PermitAll
+public class JavaxAnnotatedStub {
+
+ @DenyAll
+ public void denyMe() {}
+
+ @PermitAll
+ public void allowMe() {}
+
+ @RolesAllowed("Role1")
+ public void rolesAllowed() {}
+
+ @RequiresGuest
+ public void requiresGuest() {}
+}