SHIRO-392 Added JAX-RS support module

Based on the work of: Stig Inge Lea Bjørnsen on the Apache licensed project 
silb/shiro-jersey


Project: http://git-wip-us.apache.org/repos/asf/shiro/repo
Commit: http://git-wip-us.apache.org/repos/asf/shiro/commit/4bf2e4ed
Tree: http://git-wip-us.apache.org/repos/asf/shiro/tree/4bf2e4ed
Diff: http://git-wip-us.apache.org/repos/asf/shiro/diff/4bf2e4ed

Branch: refs/heads/1.4.x
Commit: 4bf2e4ed273d3af4aad07e59f7ed50f2308817f0
Parents: 9513183
Author: Brian Demers <bdem...@apache.org>
Authored: Tue Oct 11 14:50:22 2016 -0400
Committer: Brian Demers <bdem...@apache.org>
Committed: Fri Oct 14 15:15:51 2016 -0400

----------------------------------------------------------------------
 pom.xml                                         |  17 +-
 samples/jaxrs/pom.xml                           | 204 +++++++++++++++++++
 .../shiro/sample/jaxrs/SampleApplication.java   |  50 +++++
 .../sample/jaxrs/resources/HelloResource.java   |  37 ++++
 .../sample/jaxrs/resources/SecureResource.java  |  75 +++++++
 samples/jaxrs/src/main/resources/logback.xml    |  33 +++
 samples/jaxrs/src/main/resources/shiro.ini      |  43 ++++
 .../jaxrs/src/main/webapp/WEB-INF/web.cxf.xml   |  41 ++++
 .../web/jaxrs/ContainerIntegrationIT.groovy     | 132 ++++++++++++
 .../shiro/web/jaxrs/AbstractContainerIT.java    | 148 ++++++++++++++
 samples/pom.xml                                 |   1 +
 support/jaxrs/pom.xml                           |  62 ++++++
 .../jaxrs/AnnotationAuthorizationFilter.java    |  82 ++++++++
 .../apache/shiro/web/jaxrs/ExceptionMapper.java |  48 +++++
 .../web/jaxrs/ShiroAnnotationFilterFeature.java |  71 +++++++
 .../apache/shiro/web/jaxrs/ShiroFeature.java    |  61 ++++++
 .../shiro/web/jaxrs/ShiroSecurityContext.java   | 121 +++++++++++
 .../jaxrs/SubjectPrincipalRequestFilter.java    |  41 ++++
 .../shiro/web/jaxrs/ExceptionMapperTest.groovy  |  64 ++++++
 .../web/jaxrs/ShiroSecurityContextTest.groovy   | 204 +++++++++++++++++++
 .../SubjectPrincipalRequestFilterTest.groovy    |  50 +++++
 support/pom.xml                                 |   1 +
 22 files changed, 1585 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/shiro/blob/4bf2e4ed/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 8706e6a..ea94c99 100644
--- a/pom.xml
+++ b/pom.xml
@@ -89,7 +89,7 @@
         <!-- Don't change this version without also changing the shiro-quartz 
and shiro-features
              modules' OSGi metadata: -->
         <quartz.version>1.6.1</quartz.version>
-        <slf4j.version>1.6.4</slf4j.version>
+        <slf4j.version>1.7.21</slf4j.version>
         <spring.version>3.1.0.RELEASE</spring.version>
         <spring-boot.version>1.4.0.RELEASE</spring-boot.version>
         <guice.version>3.0</guice.version>
@@ -211,6 +211,11 @@
         <pluginManagement>
             <plugins>
                 <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-war-plugin</artifactId>
+                    <version>3.0.0</version>
+                </plugin>
+                <plugin>
                     <groupId>org.apache.felix</groupId>
                     <artifactId>maven-bundle-plugin</artifactId>
                     <version>2.1.0</version>
@@ -619,6 +624,11 @@
             </dependency>
             <dependency>
                 <groupId>org.apache.shiro</groupId>
+                <artifactId>shiro-jaxrs</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.shiro</groupId>
                 <artifactId>shiro-all</artifactId>
                 <version>${project.version}</version>
             </dependency>
@@ -692,6 +702,11 @@
                 <version>${crowd.version}</version>
             </dependency>
             <dependency>
+                <groupId>ch.qos.logback</groupId>
+                <artifactId>logback-classic</artifactId>
+                <version>1.1.7</version>
+            </dependency>
+            <dependency>
                 <groupId>org.slf4j</groupId>
                 <artifactId>slf4j-api</artifactId>
                 <version>${slf4j.version}</version>

http://git-wip-us.apache.org/repos/asf/shiro/blob/4bf2e4ed/samples/jaxrs/pom.xml
----------------------------------------------------------------------
diff --git a/samples/jaxrs/pom.xml b/samples/jaxrs/pom.xml
new file mode 100644
index 0000000..c2ef72f
--- /dev/null
+++ b/samples/jaxrs/pom.xml
@@ -0,0 +1,204 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+<!--suppress osmorcNonOsgiMavenDependency -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/maven-v4_0_0.xsd";>
+
+    <parent>
+        <groupId>org.apache.shiro.samples</groupId>
+        <artifactId>shiro-samples</artifactId>
+        <version>1.4.0-SNAPSHOT</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>samples-jaxrs</artifactId>
+    <name>Apache Shiro :: Samples :: JAX-RS</name>
+    <packaging>war</packaging>
+
+
+    <properties>
+        <jetty.version>9.3.0.M1</jetty.version>
+    </properties>
+
+    <build>
+        <pluginManagement>
+            <plugins>
+                <plugin>
+                    <groupId>org.eclipse.jetty</groupId>
+                    <artifactId>jetty-maven-plugin</artifactId>
+                    <version>${jetty.version}</version>
+                    <configuration>
+                        <contextPath>/</contextPath>
+                        <httpConnector>
+                            <port>9080</port>
+                            <idleTimeout>60000</idleTimeout>
+                        </httpConnector>
+                        <requestLog 
implementation="org.eclipse.jetty.server.NCSARequestLog">
+                            
<filename>./target/yyyy_mm_dd.request.log</filename>
+                            <retainDays>90</retainDays>
+                            <append>true</append>
+                            <extended>false</extended>
+                            <logTimeZone>GMT</logTimeZone>
+                        </requestLog>
+                    </configuration>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+        <plugins>
+            <plugin>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <forkMode>never</forkMode>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.eclipse.jetty</groupId>
+                <artifactId>jetty-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-servlet-plugin</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-jaxrs</artifactId>
+        </dependency>
+
+        <dependency>
+            <!-- Required for any libraries that expect to call the commons 
logging APIs -->
+            <groupId>org.slf4j</groupId>
+            <artifactId>jcl-over-slf4j</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.mjeanroy</groupId>
+            <artifactId>junit-servers-jetty</artifactId>
+            <version>0.4.2</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>com.jayway.restassured</groupId>
+            <artifactId>rest-assured</artifactId>
+            <version>2.8.0</version>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>commons-logging</groupId>
+                    <artifactId>commons-logging</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+    </dependencies>
+
+    <profiles>
+        <profile>
+            <id>jersey</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <properties>
+                <jersey.version>2.23.2</jersey.version>
+            </properties>
+            <dependencies>
+                <dependency>
+                    <groupId>org.glassfish.jersey.containers</groupId>
+                    <artifactId>jersey-container-grizzly2-servlet</artifactId>
+                    <version>${jersey.version}</version>
+                </dependency>
+            </dependencies>
+        </profile>
+        <profile>
+            <id>resteasy</id>
+            <properties>
+                <resteasy.version>3.0.19.Final</resteasy.version>
+            </properties>
+            <dependencies>
+                <dependency>
+                    <groupId>org.jboss.resteasy</groupId>
+                    <artifactId>resteasy-jaxrs</artifactId>
+                    <version>${resteasy.version}</version>
+                </dependency>
+
+                <dependency>
+                    <groupId>org.jboss.resteasy</groupId>
+                    <artifactId>resteasy-servlet-initializer</artifactId>
+                    <version>${resteasy.version}</version>
+                </dependency>
+
+                <dependency>
+                    <groupId>org.jboss.resteasy</groupId>
+                    <artifactId>resteasy-jackson2-provider</artifactId>
+                    <version>${resteasy.version}</version>
+                </dependency>
+            </dependencies>
+        </profile>
+        <profile>
+            <id>cxf</id>
+            <properties>
+                <cxf.version>3.1.7</cxf.version>
+            </properties>
+            <dependencies>
+                <dependency>
+                    <groupId>org.apache.cxf</groupId>
+                    <artifactId>cxf-rt-rs-http-sci</artifactId>
+                    <version>${cxf.version}</version>
+                </dependency>
+                <dependency>
+                    <groupId>org.apache.cxf</groupId>
+                    <artifactId>cxf-rt-frontend-jaxws</artifactId>
+                    <version>${cxf.version}</version>
+                </dependency>
+            </dependencies>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.eclipse.jetty</groupId>
+                        <artifactId>jetty-maven-plugin</artifactId>
+                        <configuration>
+                            <webApp>
+                                
<descriptor>src/main/webapp/WEB-INF/web.cxf.xml</descriptor>
+                            </webApp>
+                        </configuration>
+                    </plugin>
+                    <plugin>
+                        <artifactId>maven-war-plugin</artifactId>
+                        <configuration>
+                            
<webXml>src/main/webapp/WEB-INF/web.cxf.xml</webXml>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+
+</project>

http://git-wip-us.apache.org/repos/asf/shiro/blob/4bf2e4ed/samples/jaxrs/src/main/java/org/apache/shiro/sample/jaxrs/SampleApplication.java
----------------------------------------------------------------------
diff --git 
a/samples/jaxrs/src/main/java/org/apache/shiro/sample/jaxrs/SampleApplication.java
 
b/samples/jaxrs/src/main/java/org/apache/shiro/sample/jaxrs/SampleApplication.java
new file mode 100644
index 0000000..9400af0
--- /dev/null
+++ 
b/samples/jaxrs/src/main/java/org/apache/shiro/sample/jaxrs/SampleApplication.java
@@ -0,0 +1,50 @@
+/*
+ * 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.sample.jaxrs;
+
+import org.apache.shiro.sample.jaxrs.resources.HelloResource;
+import org.apache.shiro.sample.jaxrs.resources.SecureResource;
+import org.apache.shiro.web.jaxrs.ShiroFeature;
+
+import javax.ws.rs.ApplicationPath;
+import javax.ws.rs.core.Application;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Simple JAX-RS {@link Application} that is implementation agnostic.
+ * @since 1.4
+ */
+@ApplicationPath("/")
+public class SampleApplication extends Application {
+
+    @Override
+    public Set<Class<?>> getClasses() {
+        Set<Class<?>> classes = new HashSet<Class<?>>();
+
+        // register Shiro
+        classes.add(ShiroFeature.class);
+
+        // register resources
+        classes.add(HelloResource.class);
+        classes.add(SecureResource.class);
+
+        return classes;
+    }
+}

http://git-wip-us.apache.org/repos/asf/shiro/blob/4bf2e4ed/samples/jaxrs/src/main/java/org/apache/shiro/sample/jaxrs/resources/HelloResource.java
----------------------------------------------------------------------
diff --git 
a/samples/jaxrs/src/main/java/org/apache/shiro/sample/jaxrs/resources/HelloResource.java
 
b/samples/jaxrs/src/main/java/org/apache/shiro/sample/jaxrs/resources/HelloResource.java
new file mode 100644
index 0000000..458fda5
--- /dev/null
+++ 
b/samples/jaxrs/src/main/java/org/apache/shiro/sample/jaxrs/resources/HelloResource.java
@@ -0,0 +1,37 @@
+/*
+ * 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.sample.jaxrs.resources;
+
+
+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;
+
+@Path("say")
+public class HelloResource {
+
+
+    @Produces({"application/json","plain/text"})
+    @GET
+    public String saySomething(@QueryParam("words") @DefaultValue("Hello!") 
String words) {
+        return words;
+    }
+}

http://git-wip-us.apache.org/repos/asf/shiro/blob/4bf2e4ed/samples/jaxrs/src/main/java/org/apache/shiro/sample/jaxrs/resources/SecureResource.java
----------------------------------------------------------------------
diff --git 
a/samples/jaxrs/src/main/java/org/apache/shiro/sample/jaxrs/resources/SecureResource.java
 
b/samples/jaxrs/src/main/java/org/apache/shiro/sample/jaxrs/resources/SecureResource.java
new file mode 100644
index 0000000..59650ee
--- /dev/null
+++ 
b/samples/jaxrs/src/main/java/org/apache/shiro/sample/jaxrs/resources/SecureResource.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.sample.jaxrs.resources;
+
+
+import org.apache.shiro.authz.annotation.RequiresAuthentication;
+import org.apache.shiro.authz.annotation.RequiresGuest;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.apache.shiro.authz.annotation.RequiresRoles;
+import org.apache.shiro.authz.annotation.RequiresUser;
+
+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;
+
+@Path("secure")
+@Produces({"application/json","plain/text"})
+public class SecureResource {
+
+
+    @RequiresPermissions("lightsaber:requiresPermissions")
+    @Path("RequiresPermissions")
+    @GET
+    public String protectedByRequiresPermissions() {
+        return "protected";
+    }
+
+    @RequiresRoles("admin")
+    @Path("RequiresRoles")
+    @GET
+    public String protectedByRequiresRoles() {
+        return "protected";
+    }
+
+    @RequiresUser
+    @Path("RequiresUser")
+    @GET
+    public String protectedByRequiresUser() {
+        return "protected";
+    }
+
+    @RequiresGuest
+    @Path("RequiresGuest")
+    @GET
+    public String protectedByRequiresGuest() {
+        return "not protected";
+    }
+
+    @RequiresAuthentication
+    @Path("RequiresAuthentication")
+    @GET
+    public String protectedByRequiresAuthentication() {
+        return "protected";
+    }
+
+
+}

http://git-wip-us.apache.org/repos/asf/shiro/blob/4bf2e4ed/samples/jaxrs/src/main/resources/logback.xml
----------------------------------------------------------------------
diff --git a/samples/jaxrs/src/main/resources/logback.xml 
b/samples/jaxrs/src/main/resources/logback.xml
new file mode 100644
index 0000000..a627407
--- /dev/null
+++ b/samples/jaxrs/src/main/resources/logback.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+<configuration>
+
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - 
%msg%n</pattern>
+        </encoder>
+    </appender>
+
+    <root level="INFO">
+        <appender-ref ref="STDOUT" />
+    </root>
+
+    <logger name="org.apache.shiro.web.jaxrs" level="DEBUG"/>
+</configuration>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/shiro/blob/4bf2e4ed/samples/jaxrs/src/main/resources/shiro.ini
----------------------------------------------------------------------
diff --git a/samples/jaxrs/src/main/resources/shiro.ini 
b/samples/jaxrs/src/main/resources/shiro.ini
new file mode 100644
index 0000000..54fa949
--- /dev/null
+++ b/samples/jaxrs/src/main/resources/shiro.ini
@@ -0,0 +1,43 @@
+#
+# 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.
+
+[main]
+cacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManager
+
+sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager
+sessionManager.sessionIdUrlRewritingEnabled = false
+
+securityManager.sessionManager = $sessionManager
+securityManager.cacheManager = $cacheManager
+
+[urls]
+/** = authcBasic[permissive]
+
+[users]
+# format: username = password, role1, role2, ..., roleN
+root = secret,admin
+guest = guest,guest
+presidentskroob = 12345,president
+darkhelmet = ludicrousspeed,darklord,schwartz
+lonestarr = vespa,goodguy,schwartz
+
+[roles]
+# format: roleName = permission1, permission2, ..., permissionN
+admin = *
+schwartz = lightsaber:*
+goodguy = winnebago:drive:eagle5

http://git-wip-us.apache.org/repos/asf/shiro/blob/4bf2e4ed/samples/jaxrs/src/main/webapp/WEB-INF/web.cxf.xml
----------------------------------------------------------------------
diff --git a/samples/jaxrs/src/main/webapp/WEB-INF/web.cxf.xml 
b/samples/jaxrs/src/main/webapp/WEB-INF/web.cxf.xml
new file mode 100644
index 0000000..f39600f
--- /dev/null
+++ b/samples/jaxrs/src/main/webapp/WEB-INF/web.cxf.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<web-app version="3.1"
+         xmlns="http://xmlns.jcp.org/xml/ns/javaee";
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd";>
+
+    <servlet>
+        <servlet-name>CXFServlet</servlet-name>
+        <display-name>CXF Servlet</display-name>
+        
<servlet-class>org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet</servlet-class>
+        <init-param>
+            <param-name>javax.ws.rs.Application</param-name>
+            
<param-value>org.apache.shiro.sample.jaxrs.SampleApplication</param-value>
+        </init-param>
+        <load-on-startup>1</load-on-startup>
+    </servlet>
+    <servlet-mapping>
+        <servlet-name>CXFServlet</servlet-name>
+        <url-pattern>/*</url-pattern>
+    </servlet-mapping>
+
+</web-app>

http://git-wip-us.apache.org/repos/asf/shiro/blob/4bf2e4ed/samples/jaxrs/src/test/groovy/org/apache/shiro/web/jaxrs/ContainerIntegrationIT.groovy
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..6fb66c0
--- /dev/null
+++ 
b/samples/jaxrs/src/test/groovy/org/apache/shiro/web/jaxrs/ContainerIntegrationIT.groovy
@@ -0,0 +1,132 @@
+/*
+ * 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.junit.Test;
+
+import static com.jayway.restassured.RestAssured.*
+import static org.hamcrest.Matchers.*
+
+public class ContainerIntegrationIT extends AbstractContainerIT {
+
+    @Test
+    void testNoAuthResource() {
+
+        get(getBaseUri() + "say")
+            .then()
+                .assertThat()
+                .statusCode(is(200)).and()
+                .body(equalTo("Hello!"))
+    }
+
+    @Test
+    void testSecuredRequiresAuthentication() {
+
+        get(getBaseUri() + "secure/RequiresAuthentication")
+            .then()
+                .assertThat().statusCode(is(401))
+
+        given()
+            .header("Authorization", getBasicAuthorizationHeaderValue("root", 
"secret"))
+        .when()
+            .get(getBaseUri() + "secure/RequiresAuthentication")
+        .then()
+            .assertThat()
+                .statusCode(is(200)).and()
+                .body(equalTo("protected"))
+    }
+
+    @Test
+    void testSecuredRequiresUser() {
+
+        get(getBaseUri() + "secure/RequiresUser")
+            .then()
+                .assertThat().statusCode(is(401))
+
+        given()
+            .header("Authorization", getBasicAuthorizationHeaderValue("root", 
"secret"))
+        .when()
+            .get(getBaseUri() + "secure/RequiresUser")
+        .then()
+            .assertThat()
+                .statusCode(is(200)).and()
+                .body(equalTo("protected"))
+    }
+
+    @Test
+    void testSecuredRequiresRoles() {
+
+        get(getBaseUri() + "secure/RequiresRoles")
+            .then()
+                .assertThat().statusCode(is(401))
+
+        given()
+                .header("Authorization", 
getBasicAuthorizationHeaderValue("guest", "guest"))
+        .when()
+            .get(getBaseUri() + "secure/RequiresRoles")
+        .then()
+            .assertThat()
+                .statusCode(is(403)).and()
+
+        given()
+            .header("Authorization", getBasicAuthorizationHeaderValue("root", 
"secret"))
+        .when()
+            .get(getBaseUri() + "secure/RequiresRoles")
+        .then()
+            .assertThat()
+                .statusCode(is(200)).and()
+                .body(equalTo("protected"))
+    }
+
+    @Test
+    void testSecuredRequiresPermissions() {
+
+        get(getBaseUri() + "secure/RequiresPermissions")
+            .then()
+                .assertThat().statusCode(is(401))
+
+        given()
+            .header("Authorization", getBasicAuthorizationHeaderValue("guest", 
"guest"))
+        .when()
+            .get(getBaseUri() + "secure/RequiresPermissions")
+        .then()
+            .assertThat()
+                .statusCode(is(403)).and()
+
+        given()
+            .header("Authorization", 
getBasicAuthorizationHeaderValue("lonestarr", "vespa"))
+        .when()
+            .get(getBaseUri() + "secure/RequiresPermissions")
+        .then()
+            .assertThat()
+                .statusCode(is(200)).and()
+                .body(equalTo("protected"))
+    }
+
+    @Test
+    void testSecuredRequiresGuest() {
+
+        get(getBaseUri() + "secure/RequiresGuest")
+            .then()
+                .assertThat()
+                    .statusCode(is(200)).and()
+                    .body(equalTo("not protected"))
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/shiro/blob/4bf2e4ed/samples/jaxrs/src/test/java/org/apache/shiro/web/jaxrs/AbstractContainerIT.java
----------------------------------------------------------------------
diff --git 
a/samples/jaxrs/src/test/java/org/apache/shiro/web/jaxrs/AbstractContainerIT.java
 
b/samples/jaxrs/src/test/java/org/apache/shiro/web/jaxrs/AbstractContainerIT.java
new file mode 100644
index 0000000..192f9e3
--- /dev/null
+++ 
b/samples/jaxrs/src/test/java/org/apache/shiro/web/jaxrs/AbstractContainerIT.java
@@ -0,0 +1,148 @@
+/*
+ * 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 com.github.mjeanroy.junit.servers.jetty.EmbeddedJetty;
+import com.github.mjeanroy.junit.servers.jetty.EmbeddedJettyConfiguration;
+import org.apache.shiro.codec.Base64;
+import org.eclipse.jetty.annotations.AnnotationConfiguration;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.resource.FileResource;
+import org.eclipse.jetty.webapp.Configuration;
+import org.eclipse.jetty.webapp.FragmentConfiguration;
+import org.eclipse.jetty.webapp.JettyWebXmlConfiguration;
+import org.eclipse.jetty.webapp.MetaInfConfiguration;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.webapp.WebInfConfiguration;
+import org.eclipse.jetty.webapp.WebXmlConfiguration;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.UnsupportedEncodingException;
+
+import static com.github.mjeanroy.junit.servers.commons.Strings.isNotBlank;
+import static org.eclipse.jetty.util.resource.Resource.newResource;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public abstract class AbstractContainerIT {
+
+    private static EmbeddedJetty jetty;
+
+    private static int port = 0;
+
+    @BeforeClass
+    public static void startContainer() throws Exception {
+
+        EmbeddedJettyConfiguration config = 
EmbeddedJettyConfiguration.builder()
+                .withWebapp(getWarDir())
+                .build();
+
+        jetty = new EmbeddedJetty(config) {
+
+            /**
+             * Overriding with contents of this pull request, to make fragment 
scanning work.
+             * https://github.com/mjeanroy/junit-servers/pull/3
+             */
+            protected WebAppContext createdWebAppContext() throws Exception {
+                final String path = configuration.getPath();
+                final String webapp = configuration.getWebapp();
+                final String classpath = configuration.getClasspath();
+
+                WebAppContext ctx = new WebAppContext();
+                
ctx.setClassLoader(Thread.currentThread().getContextClassLoader());
+                ctx.setContextPath(path);
+
+                // Useful for WebXmlConfiguration
+                ctx.setBaseResource(newResource(webapp));
+
+                ctx.setConfigurations(new Configuration[]{
+                        new WebInfConfiguration(),
+                        new WebXmlConfiguration(),
+                        new AnnotationConfiguration(),
+                        new JettyWebXmlConfiguration(),
+                        new MetaInfConfiguration(),
+                        new FragmentConfiguration(),
+                });
+
+                if (isNotBlank(classpath)) {
+                    // Fix to scan Spring WebApplicationInitializer
+                    // This will add compiled classes to jetty classpath
+                    // See: 
http://stackoverflow.com/questions/13222071/spring-3-1-webapplicationinitializer-embedded-jetty-8-annotationconfiguration
+                    // And more precisely: 
http://stackoverflow.com/a/18449506/1215828
+                    File classes = new File(classpath);
+                    FileResource containerResources = new 
FileResource(classes.toURI());
+                    ctx.getMetaData().addContainerResource(containerResources);
+                }
+
+                Server server = getDelegate();
+
+                ctx.setParentLoaderPriority(true);
+                ctx.setWar(webapp);
+                ctx.setServer(server);
+
+                // Add server context
+                server.setHandler(ctx);
+
+                return ctx;
+            }
+        };
+
+        jetty.start();
+        port = jetty.getPort();
+
+        assertTrue(jetty.isStarted());
+    }
+
+    protected static String getBaseUri() {
+        return "http://localhost:"; + port + "/";
+    }
+
+    protected static String getWarDir() {
+        File[] warFiles = new File("target").listFiles(new FilenameFilter() {
+            @Override
+            public boolean accept(File dir, String name) {
+                return name.endsWith(".war");
+            }
+        });
+
+        assertEquals("Expected only one war file in target directory, run 'mvn 
clean' and try again", 1, warFiles.length);
+
+        return warFiles[0].getAbsolutePath().replaceFirst("\\.war$", "");
+    }
+
+    @AfterClass
+    public static void stopContainer() {
+        if (jetty != null) {
+            jetty.stop();
+        }
+    }
+
+    protected static String getBasicAuthorizationHeaderValue(String username, 
String password) throws UnsupportedEncodingException {
+        String authorizationHeader = username + ":" + password;
+        byte[] valueBytes;
+        valueBytes = authorizationHeader.getBytes("UTF-8");
+        authorizationHeader = new String(Base64.encode(valueBytes));
+        return "Basic " + authorizationHeader;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/shiro/blob/4bf2e4ed/samples/pom.xml
----------------------------------------------------------------------
diff --git a/samples/pom.xml b/samples/pom.xml
index 29c5369..7bd9e83 100644
--- a/samples/pom.xml
+++ b/samples/pom.xml
@@ -43,6 +43,7 @@
         <module>guice</module>
         <module>quickstart-guice</module>
         <module>servlet-plugin</module>
+        <module>jaxrs</module>
     </modules>
 
     <reporting>

http://git-wip-us.apache.org/repos/asf/shiro/blob/4bf2e4ed/support/jaxrs/pom.xml
----------------------------------------------------------------------
diff --git a/support/jaxrs/pom.xml b/support/jaxrs/pom.xml
new file mode 100644
index 0000000..0c51641
--- /dev/null
+++ b/support/jaxrs/pom.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/maven-v4_0_0.xsd";>
+
+    <parent>
+        <groupId>org.apache.shiro</groupId>
+        <artifactId>shiro-root</artifactId>
+        <version>1.4.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>shiro-jaxrs</artifactId>
+    <name>Apache Shiro :: Support :: JAX-RS</name>
+    <packaging>jar</packaging>
+
+
+    <dependencies>
+
+        <!-- Shiro Deps -->
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>javax.ws.rs</groupId>
+            <artifactId>javax.ws.rs-api</artifactId>
+            <version>2.0.1</version>
+        </dependency>
+
+        <!-- Test Deps -->
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>jcl-over-slf4j</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+
+</project>

http://git-wip-us.apache.org/repos/asf/shiro/blob/4bf2e4ed/support/jaxrs/src/main/java/org/apache/shiro/web/jaxrs/AnnotationAuthorizationFilter.java
----------------------------------------------------------------------
diff --git 
a/support/jaxrs/src/main/java/org/apache/shiro/web/jaxrs/AnnotationAuthorizationFilter.java
 
b/support/jaxrs/src/main/java/org/apache/shiro/web/jaxrs/AnnotationAuthorizationFilter.java
new file mode 100644
index 0000000..14c9183
--- /dev/null
+++ 
b/support/jaxrs/src/main/java/org/apache/shiro/web/jaxrs/AnnotationAuthorizationFilter.java
@@ -0,0 +1,82 @@
+/*
+ * 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.RequiresAuthentication;
+import org.apache.shiro.authz.annotation.RequiresGuest;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.apache.shiro.authz.annotation.RequiresRoles;
+import org.apache.shiro.authz.annotation.RequiresUser;
+import org.apache.shiro.authz.aop.AuthenticatedAnnotationHandler;
+import org.apache.shiro.authz.aop.AuthorizingAnnotationHandler;
+import org.apache.shiro.authz.aop.GuestAnnotationHandler;
+import org.apache.shiro.authz.aop.PermissionAnnotationHandler;
+import org.apache.shiro.authz.aop.RoleAnnotationHandler;
+import org.apache.shiro.authz.aop.UserAnnotationHandler;
+
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.ext.Provider;
+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 
Shiro annotations on it.
+ *
+ * @see org.apache.shiro.authz.annotation
+ * @since 1.4
+ */
+public class AnnotationAuthorizationFilter implements ContainerRequestFilter {
+
+    private final Map<AuthorizingAnnotationHandler, Annotation> authzChecks;
+
+    public AnnotationAuthorizationFilter(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 (RequiresPermissions.class.equals(t)) return new 
PermissionAnnotationHandler();
+        else if (RequiresRoles.class.equals(t)) return new 
RoleAnnotationHandler();
+        else if (RequiresUser.class.equals(t)) return new 
UserAnnotationHandler();
+        else if (RequiresGuest.class.equals(t)) return new 
GuestAnnotationHandler();
+        else if (RequiresAuthentication.class.equals(t)) return new 
AuthenticatedAnnotationHandler();
+        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

http://git-wip-us.apache.org/repos/asf/shiro/blob/4bf2e4ed/support/jaxrs/src/main/java/org/apache/shiro/web/jaxrs/ExceptionMapper.java
----------------------------------------------------------------------
diff --git 
a/support/jaxrs/src/main/java/org/apache/shiro/web/jaxrs/ExceptionMapper.java 
b/support/jaxrs/src/main/java/org/apache/shiro/web/jaxrs/ExceptionMapper.java
new file mode 100644
index 0000000..ec6fb64
--- /dev/null
+++ 
b/support/jaxrs/src/main/java/org/apache/shiro/web/jaxrs/ExceptionMapper.java
@@ -0,0 +1,48 @@
+/*
+ * 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.AuthorizationException;
+import org.apache.shiro.authz.UnauthorizedException;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+/**
+ * JAX-RS exception mapper used to map Shiro {@link AuthorizationExceptions} 
to HTTP status codes.
+ * {@link UnauthorizedException} will be mapped to 403, all others 401.
+ * @since 1.4
+ */
+public class ExceptionMapper implements 
javax.ws.rs.ext.ExceptionMapper<AuthorizationException> {
+
+    @Override
+    public Response toResponse(AuthorizationException exception) {
+
+        Status status;
+
+        if (exception instanceof UnauthorizedException) {
+            status = Status.FORBIDDEN;
+        } else {
+            status = Status.UNAUTHORIZED;
+        }
+
+        return Response.status(status).build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/shiro/blob/4bf2e4ed/support/jaxrs/src/main/java/org/apache/shiro/web/jaxrs/ShiroAnnotationFilterFeature.java
----------------------------------------------------------------------
diff --git 
a/support/jaxrs/src/main/java/org/apache/shiro/web/jaxrs/ShiroAnnotationFilterFeature.java
 
b/support/jaxrs/src/main/java/org/apache/shiro/web/jaxrs/ShiroAnnotationFilterFeature.java
new file mode 100644
index 0000000..9d23b7b
--- /dev/null
+++ 
b/support/jaxrs/src/main/java/org/apache/shiro/web/jaxrs/ShiroAnnotationFilterFeature.java
@@ -0,0 +1,71 @@
+/*
+ * 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.RequiresAuthentication;
+import org.apache.shiro.authz.annotation.RequiresGuest;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.apache.shiro.authz.annotation.RequiresRoles;
+import org.apache.shiro.authz.annotation.RequiresUser;
+import org.apache.shiro.web.filter.authz.AuthorizationFilter;
+
+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 ShiroAnnotationFilterFeature implements DynamicFeature {
+
+    private static List<Class<? extends Annotation>> shiroAnnotations = 
Collections.unmodifiableList(Arrays.asList(
+            RequiresPermissions.class,
+            RequiresRoles.class,
+            RequiresAuthentication.class,
+            RequiresUser.class,
+            RequiresGuest.class));
+
+    @Override
+    public void configure(ResourceInfo resourceInfo, FeatureContext context) {
+
+        List<Annotation> authzSpecs = new ArrayList<Annotation>();
+
+        for (Class<? extends Annotation> annotationClass : shiroAnnotations) {
+            // 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 AnnotationAuthorizationFilter(authzSpecs), 
Priorities.AUTHORIZATION);
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/shiro/blob/4bf2e4ed/support/jaxrs/src/main/java/org/apache/shiro/web/jaxrs/ShiroFeature.java
----------------------------------------------------------------------
diff --git 
a/support/jaxrs/src/main/java/org/apache/shiro/web/jaxrs/ShiroFeature.java 
b/support/jaxrs/src/main/java/org/apache/shiro/web/jaxrs/ShiroFeature.java
new file mode 100644
index 0000000..0a4718b
--- /dev/null
+++ b/support/jaxrs/src/main/java/org/apache/shiro/web/jaxrs/ShiroFeature.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shiro.web.jaxrs;
+
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.Feature;
+import javax.ws.rs.core.FeatureContext;
+import javax.ws.rs.ext.Provider;
+
+
+/**
+ * Shiro JAX-RS feature which includes {@link ExceptionMapper}, {@link 
SubjectPrincipalRequestFilter}, and
+ * {@link ShiroAnnotationFilterFeature}.
+ *
+ * Typically a JAX-RS {@link Application} class will include this Feature 
class in the
+ * classes returned from {@link Application#getClasses()} method, for example:
+ * <blockquote><pre>
+ *     public class SampleApplication extends Application {
+ *
+ *         @Override
+ *         public Set<Class<?>> getClasses() {
+ *             Set<Class<?>> classes = new HashSet<Class<?>>();
+ *
+ *             // register Shiro
+ *             classes.add(ShiroFeature.class);
+ *             ...
+ *             return classes;
+ *         }
+ *     }
+ * </pre></blockquote>
+ * @since 1.4
+ */
+@Provider // NOTE: Apache CXF requires this annotation on this feature (jersey 
and resteasy do not)
+public class ShiroFeature implements Feature {
+
+    @Override
+    public boolean configure(FeatureContext context) {
+
+        context.register(ExceptionMapper.class);
+        context.register(SubjectPrincipalRequestFilter.class);
+        context.register(ShiroAnnotationFilterFeature.class);
+
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/shiro/blob/4bf2e4ed/support/jaxrs/src/main/java/org/apache/shiro/web/jaxrs/ShiroSecurityContext.java
----------------------------------------------------------------------
diff --git 
a/support/jaxrs/src/main/java/org/apache/shiro/web/jaxrs/ShiroSecurityContext.java
 
b/support/jaxrs/src/main/java/org/apache/shiro/web/jaxrs/ShiroSecurityContext.java
new file mode 100644
index 0000000..867ce48
--- /dev/null
+++ 
b/support/jaxrs/src/main/java/org/apache/shiro/web/jaxrs/ShiroSecurityContext.java
@@ -0,0 +1,121 @@
+/*
+ * 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.SecurityUtils;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.subject.Subject;
+
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.core.SecurityContext;
+import java.security.Principal;
+
+/**
+ * A Shiro based {@link SecurityContext} that exposes the current Shiro {@link 
Subject} as a {@link Principal}.
+ * The {@link #isUserInRole(String)} method returns the result of {@link 
Subject#hasRole(String)}.
+ *
+ * @since 1.4
+ */
+public class ShiroSecurityContext implements SecurityContext {
+
+    final private ContainerRequestContext containerRequestContext;
+
+    public ShiroSecurityContext(ContainerRequestContext 
containerRequestContext) {
+        this.containerRequestContext = containerRequestContext;
+    }
+
+    @Override
+    public Principal getUserPrincipal() {
+
+        Principal result;
+
+        Subject subject = getSubject();
+        PrincipalCollection shiroPrincipals = subject.getPrincipals();
+        if (shiroPrincipals != null) {
+            result = shiroPrincipals.oneByType(Principal.class);
+
+            if (result == null) {
+                result = new 
ObjectPrincipal(shiroPrincipals.getPrimaryPrincipal());
+            }
+        }
+        else {
+            result = 
containerRequestContext.getSecurityContext().getUserPrincipal();
+        }
+
+        return result;
+    }
+
+    @Override
+    public boolean isUserInRole(String role) {
+        return getSubject().hasRole(role);
+    }
+
+    @Override
+    public boolean isSecure() {
+        return containerRequestContext.getSecurityContext().isSecure();
+    }
+
+    @Override
+    public String getAuthenticationScheme() {
+        return 
containerRequestContext.getSecurityContext().getAuthenticationScheme();
+    }
+
+    private Subject getSubject() {
+        return SecurityUtils.getSubject();
+    }
+
+
+    /**
+     * Java Principal wrapper around any Shiro Principal object.s
+     */
+    private class ObjectPrincipal implements Principal {
+        private Object object = null;
+
+        public ObjectPrincipal(Object object) {
+            this.object = object;
+        }
+
+        public Object getObject() {
+            return object;
+        }
+
+        public String getName() {
+            return getObject().toString();
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            ObjectPrincipal that = (ObjectPrincipal) o;
+
+            return object.equals(that.object);
+
+        }
+
+        public int hashCode() {
+            return object.hashCode();
+        }
+
+        public String toString() {
+            return object.toString();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/shiro/blob/4bf2e4ed/support/jaxrs/src/main/java/org/apache/shiro/web/jaxrs/SubjectPrincipalRequestFilter.java
----------------------------------------------------------------------
diff --git 
a/support/jaxrs/src/main/java/org/apache/shiro/web/jaxrs/SubjectPrincipalRequestFilter.java
 
b/support/jaxrs/src/main/java/org/apache/shiro/web/jaxrs/SubjectPrincipalRequestFilter.java
new file mode 100644
index 0000000..4be8aba
--- /dev/null
+++ 
b/support/jaxrs/src/main/java/org/apache/shiro/web/jaxrs/SubjectPrincipalRequestFilter.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 javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.container.PreMatching;
+import javax.ws.rs.ext.Provider;
+import java.io.IOException;
+
+/**
+ * A {@link ContainerRequestFilter} that replaces the {@link 
javax.ws.rs.core.SecurityContext}
+ * with a {@link ShiroSecurityContext}.
+ * @since 1.4
+ */
+@Provider
+@PreMatching
+public class SubjectPrincipalRequestFilter implements ContainerRequestFilter {
+
+    @Override
+    public void filter(ContainerRequestContext requestContext) throws 
IOException {
+        requestContext.setSecurityContext(new 
ShiroSecurityContext(requestContext));
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/shiro/blob/4bf2e4ed/support/jaxrs/src/test/groovy/org/apache/shiro/web/jaxrs/ExceptionMapperTest.groovy
----------------------------------------------------------------------
diff --git 
a/support/jaxrs/src/test/groovy/org/apache/shiro/web/jaxrs/ExceptionMapperTest.groovy
 
b/support/jaxrs/src/test/groovy/org/apache/shiro/web/jaxrs/ExceptionMapperTest.groovy
new file mode 100644
index 0000000..8aa3c18
--- /dev/null
+++ 
b/support/jaxrs/src/test/groovy/org/apache/shiro/web/jaxrs/ExceptionMapperTest.groovy
@@ -0,0 +1,64 @@
+/*
+ * 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.AuthorizationException
+import org.apache.shiro.authz.UnauthorizedException
+import org.junit.Test
+
+import javax.ws.rs.core.Response
+import javax.ws.rs.ext.RuntimeDelegate
+
+import static org.junit.Assert.*
+import static org.easymock.EasyMock.*
+
+/**
+ * Tests for {@link ExceptionMapper}.
+ * @since 1.4
+ */
+class ExceptionMapperTest {
+
+    @Test
+    void testUnauthorizedException() {
+
+        doTest(new UnauthorizedException("expected test exception."), 
Response.Status.FORBIDDEN)
+        doTest(new AuthorizationException("expected test exception."), 
Response.Status.UNAUTHORIZED)
+        doTest(null, Response.Status.UNAUTHORIZED)
+    }
+
+    private void doTest(AuthorizationException exception , Response.StatusType 
expectedStatus) {
+        def runtimeDelegate = strictMock(RuntimeDelegate)
+
+        RuntimeDelegate.setInstance(runtimeDelegate)
+
+        def responseBuilder = strictMock(Response.ResponseBuilder)
+        def response = strictMock(Response)
+
+        
expect(runtimeDelegate.createResponseBuilder()).andReturn(responseBuilder).anyTimes()
+        expect(responseBuilder.status((Response.StatusType) 
expectedStatus)).andReturn(responseBuilder)
+        expect(responseBuilder.build()).andReturn(response)
+
+        replay runtimeDelegate, responseBuilder
+
+        def responseResult = new ExceptionMapper().toResponse(exception)
+        assertSame response, responseResult
+
+        verify runtimeDelegate, responseBuilder
+    }
+}

http://git-wip-us.apache.org/repos/asf/shiro/blob/4bf2e4ed/support/jaxrs/src/test/groovy/org/apache/shiro/web/jaxrs/ShiroSecurityContextTest.groovy
----------------------------------------------------------------------
diff --git 
a/support/jaxrs/src/test/groovy/org/apache/shiro/web/jaxrs/ShiroSecurityContextTest.groovy
 
b/support/jaxrs/src/test/groovy/org/apache/shiro/web/jaxrs/ShiroSecurityContextTest.groovy
new file mode 100644
index 0000000..5d7e947
--- /dev/null
+++ 
b/support/jaxrs/src/test/groovy/org/apache/shiro/web/jaxrs/ShiroSecurityContextTest.groovy
@@ -0,0 +1,204 @@
+/*
+ * 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.subject.SimplePrincipalCollection
+import org.apache.shiro.subject.Subject
+import org.apache.shiro.util.ThreadContext
+import org.junit.After
+import org.junit.Test
+
+import javax.ws.rs.container.ContainerRequestContext
+import javax.ws.rs.core.SecurityContext
+import java.security.Principal
+
+import static org.easymock.EasyMock.*
+import static org.junit.Assert.*
+
+/**
+ * Tests for {@link ShiroSecurityContext}.
+ * @since 1.4
+ */
+class ShiroSecurityContextTest {
+
+    @Test
+    void testIsSecure() {
+        def requestContext = mock(ContainerRequestContext)
+        def originalSecurityContext = mock(SecurityContext)
+        def shrioContext = new ShiroSecurityContext(requestContext)
+
+        
expect(requestContext.getSecurityContext()).andReturn(originalSecurityContext).anyTimes()
+        expect(originalSecurityContext.isSecure()).andReturn(true)
+        replay requestContext, originalSecurityContext
+
+        assertTrue shrioContext.isSecure()
+
+        verify requestContext, originalSecurityContext
+    }
+
+    @Test
+    void testGetAuthenticationScheme() {
+        def requestContext = mock(ContainerRequestContext)
+        def originalSecurityContext = mock(SecurityContext)
+        def shrioContext = new ShiroSecurityContext(requestContext)
+
+        
expect(requestContext.getSecurityContext()).andReturn(originalSecurityContext).anyTimes()
+        
expect(originalSecurityContext.getAuthenticationScheme()).andReturn("https")
+        replay requestContext, originalSecurityContext
+
+        assertEquals "https", shrioContext.getAuthenticationScheme()
+
+        verify requestContext, originalSecurityContext
+    }
+
+    @Test
+    void testGetUserPrincipalWithString() {
+        def requestContext = mock(ContainerRequestContext)
+        def originalSecurityContext = mock(SecurityContext)
+        def shrioContext = new ShiroSecurityContext(requestContext)
+        def subject = mock(Subject)
+        ThreadContext.bind(subject)
+
+        
expect(requestContext.getSecurityContext()).andReturn(originalSecurityContext).anyTimes()
+        expect(subject.getPrincipals()).andReturn(new 
SimplePrincipalCollection("TestUser", "realm"))
+
+        replay requestContext, originalSecurityContext, subject
+
+        def resultPrincipal = shrioContext.getUserPrincipal()
+        assertSame "TestUser", resultPrincipal.getName()
+
+        verify requestContext, originalSecurityContext, subject
+    }
+
+    @Test
+    void testGetUserPrincipalNoPrincipal() {
+        def requestContext = mock(ContainerRequestContext)
+        def originalSecurityContext = mock(SecurityContext)
+        def shrioContext = new ShiroSecurityContext(requestContext)
+        def subject = mock(Subject)
+        ThreadContext.bind(subject)
+
+        
expect(requestContext.getSecurityContext()).andReturn(originalSecurityContext).anyTimes()
+        expect(subject.getPrincipals()).andReturn(null)
+        expect(originalSecurityContext.getUserPrincipal()).andReturn(null)
+
+        replay requestContext, originalSecurityContext, subject
+
+        assertNull shrioContext.getUserPrincipal()
+
+        verify requestContext, originalSecurityContext, subject
+    }
+
+    @Test
+    void testGetUserPrincipalPrincipalObject() {
+        def requestContext = mock(ContainerRequestContext)
+        def originalSecurityContext = mock(SecurityContext)
+        def shrioContext = new ShiroSecurityContext(requestContext)
+        def subject = mock(Subject)
+        ThreadContext.bind(subject)
+
+        
expect(requestContext.getSecurityContext()).andReturn(originalSecurityContext).anyTimes()
+        expect(subject.getPrincipals()).andReturn(new 
SimplePrincipalCollection(new TestPrincipal("Tester"), "test-realm"))
+
+        replay requestContext, originalSecurityContext, subject
+
+        def resultPrincipal = shrioContext.getUserPrincipal()
+        assertSame "Tester", resultPrincipal.getName()
+
+        verify requestContext, originalSecurityContext, subject
+    }
+
+    @Test
+    void testUserInRoleTrue() {
+        def requestContext = mock(ContainerRequestContext)
+        def originalSecurityContext = mock(SecurityContext)
+        def shrioContext = new ShiroSecurityContext(requestContext)
+        def subject = mock(Subject)
+        ThreadContext.bind(subject)
+
+        
expect(requestContext.getSecurityContext()).andReturn(originalSecurityContext).anyTimes()
+        expect(subject.hasRole("test-role")).andReturn(true)
+
+        replay requestContext, originalSecurityContext, subject
+
+        assertTrue shrioContext.isUserInRole("test-role")
+
+        verify requestContext, originalSecurityContext, subject
+    }
+
+    @Test
+    void testUserInRoleFalse() {
+        def requestContext = mock(ContainerRequestContext)
+        def originalSecurityContext = mock(SecurityContext)
+        def shrioContext = new ShiroSecurityContext(requestContext)
+        def subject = mock(Subject)
+        ThreadContext.bind(subject)
+
+        
expect(requestContext.getSecurityContext()).andReturn(originalSecurityContext).anyTimes()
+        expect(subject.hasRole("test-role")).andReturn(false)
+
+        replay requestContext, originalSecurityContext, subject
+
+        assertFalse shrioContext.isUserInRole("test-role")
+
+        verify requestContext, originalSecurityContext, subject
+    }
+
+    @Test
+    void testPrincipalEquals() {
+        def requestContext = mock(ContainerRequestContext)
+        def originalSecurityContext = mock(SecurityContext)
+        def shrioContext = new ShiroSecurityContext(requestContext)
+        def subject = mock(Subject)
+        ThreadContext.bind(subject)
+
+        
expect(requestContext.getSecurityContext()).andReturn(originalSecurityContext).anyTimes()
+        expect(subject.getPrincipals()).andReturn(new 
SimplePrincipalCollection("Tester", "test-realm"))
+        expect(subject.getPrincipals()).andReturn(new 
SimplePrincipalCollection("Tester", "test-realm"))
+
+        replay requestContext, originalSecurityContext, subject
+
+        def result1Principal = shrioContext.getUserPrincipal()
+        def result2Principal = shrioContext.getUserPrincipal()
+
+        assertEquals result1Principal, result2Principal
+        assertNotSame result1Principal, result2Principal
+
+        verify requestContext, originalSecurityContext, subject
+    }
+
+    @After
+    void cleanUp() {
+        ThreadContext.remove()
+    }
+
+    class TestPrincipal implements Principal {
+
+        final String name;
+
+        TestPrincipal(String name) {
+            this.name = name
+        }
+
+        @Override
+        String getName() {
+            return name
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/shiro/blob/4bf2e4ed/support/jaxrs/src/test/groovy/org/apache/shiro/web/jaxrs/SubjectPrincipalRequestFilterTest.groovy
----------------------------------------------------------------------
diff --git 
a/support/jaxrs/src/test/groovy/org/apache/shiro/web/jaxrs/SubjectPrincipalRequestFilterTest.groovy
 
b/support/jaxrs/src/test/groovy/org/apache/shiro/web/jaxrs/SubjectPrincipalRequestFilterTest.groovy
new file mode 100644
index 0000000..7ae75cd
--- /dev/null
+++ 
b/support/jaxrs/src/test/groovy/org/apache/shiro/web/jaxrs/SubjectPrincipalRequestFilterTest.groovy
@@ -0,0 +1,50 @@
+/*
+ * 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.ws.rs.container.ContainerRequestContext
+
+import static org.easymock.EasyMock.*
+import static org.junit.Assert.*
+
+/**
+ * Tests for {@link SubjectPrincipalRequestFilter}.
+ * @since 1.4
+ */
+class SubjectPrincipalRequestFilterTest {
+
+    @Test
+    void testWrapContext() {
+        def filter = new SubjectPrincipalRequestFilter()
+
+        def contextCapture = new Capture<ShiroSecurityContext>()
+        def requestContext = mock(ContainerRequestContext)
+        expect(requestContext.setSecurityContext(capture(contextCapture)))
+        replay requestContext
+
+        filter.filter(requestContext)
+
+        verify requestContext
+        assertSame requestContext, contextCapture.value.containerRequestContext
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/shiro/blob/4bf2e4ed/support/pom.xml
----------------------------------------------------------------------
diff --git a/support/pom.xml b/support/pom.xml
index 9f12c69..8886f02 100644
--- a/support/pom.xml
+++ b/support/pom.xml
@@ -42,6 +42,7 @@
         <module>cas</module>
         <module>spring-boot</module>
         <module>servlet-plugin</module>
+        <module>jaxrs</module>
     </modules>
 
 </project>

Reply via email to