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/62dd1ef2 Tree: http://git-wip-us.apache.org/repos/asf/shiro/tree/62dd1ef2 Diff: http://git-wip-us.apache.org/repos/asf/shiro/diff/62dd1ef2 Branch: refs/heads/master Commit: 62dd1ef2f89beddacab64a146c65dbe853bf1011 Parents: 3942307 Author: Brian Demers <bdem...@apache.org> Authored: Tue Oct 11 14:50:22 2016 -0400 Committer: Brian Demers <bdem...@apache.org> Committed: Tue Oct 18 13:45:46 2016 -0400 ---------------------------------------------------------------------- pom.xml | 22 +- 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, 1590 insertions(+), 1 deletion(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/shiro/blob/62dd1ef2/pom.xml ---------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index 8674575..110d88b 100644 --- a/pom.xml +++ b/pom.xml @@ -90,7 +90,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> @@ -217,6 +217,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> @@ -689,6 +694,16 @@ <artifactId>shiro-spring</artifactId> <version>${project.version}</version> </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> <!-- Shiro samples: --> <dependency> @@ -778,6 +793,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/62dd1ef2/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/62dd1ef2/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/62dd1ef2/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/62dd1ef2/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/62dd1ef2/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/62dd1ef2/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/62dd1ef2/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/62dd1ef2/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/62dd1ef2/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/62dd1ef2/samples/pom.xml ---------------------------------------------------------------------- diff --git a/samples/pom.xml b/samples/pom.xml index c3a9116..bd7102b 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/62dd1ef2/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/62dd1ef2/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/62dd1ef2/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/62dd1ef2/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/62dd1ef2/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/62dd1ef2/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/62dd1ef2/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/62dd1ef2/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/62dd1ef2/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/62dd1ef2/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/62dd1ef2/support/pom.xml ---------------------------------------------------------------------- diff --git a/support/pom.xml b/support/pom.xml index 9e1b171..f54016e 100644 --- a/support/pom.xml +++ b/support/pom.xml @@ -43,6 +43,7 @@ <module>features</module> <module>spring-boot</module> <module>servlet-plugin</module> + <module>jaxrs</module> </modules> </project>