You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tomee.apache.org by jl...@apache.org on 2021/06/16 22:09:48 UTC
[tomee] branch master updated: TOMEE-3758 Jakarta Security example
with tomcat-users.xml identity store
This is an automated email from the ASF dual-hosted git repository.
jlmonteiro pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tomee.git
The following commit(s) were added to refs/heads/master by this push:
new e77be55 TOMEE-3758 Jakarta Security example with tomcat-users.xml identity store
new 57ada73 Merge branch 'master' of github.com:apache/tomee into master
e77be55 is described below
commit e77be5540121c2fa9a7925828d8bee6dcff1019f
Author: Jean-Louis Monteiro <jl...@tomitribe.com>
AuthorDate: Thu Jun 17 00:08:32 2021 +0200
TOMEE-3758 Jakarta Security example with tomcat-users.xml identity store
---
examples/pom.xml | 4 +
.../security-tomcat-user-identitystore/README.adoc | 172 +++++++++++++++++++++
.../security-tomcat-user-identitystore/pom.xml | 74 +++++++++
.../src/main/java/org/superbiz/movie/Api.java | 26 ++++
.../src/main/java/org/superbiz/movie}/Movie.java | 12 +-
.../org/superbiz/movie/MovieAdminResource.java | 76 +++++++++
.../java/org/superbiz/movie/MovieResource.java | 69 +++++++++
.../main/java/org/superbiz/movie/MovieStore.java | 63 ++++++++
.../src/main/resources/META-INF/beans.xml | 18 +++
.../src/main/resources/conf/tomcat-users.xml | 24 +++
.../src/main/webapp/WEB-INF/web.xml | 35 +++++
.../java/org/superbiz/movie/BasicAuthFilter.java | 41 +++++
.../java/org/superbiz/movie/MovieResourceTest.java | 170 ++++++++++++++++++++
13 files changed, 783 insertions(+), 1 deletion(-)
diff --git a/examples/pom.xml b/examples/pom.xml
index c4a833a..6c7b962 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -216,6 +216,10 @@
<module>cloud-tomee-azure</module>
<module>mp-faulttolerance-timeout</module>
<module>xa-datasource</module>
+
+ <!-- Jakarta Security Examples -->
+ <module>security-tomcat-user-identitystore</module>
+
</modules>
<dependencies>
<dependency>
diff --git a/examples/security-tomcat-user-identitystore/README.adoc b/examples/security-tomcat-user-identitystore/README.adoc
new file mode 100644
index 0000000..2e8c3de
--- /dev/null
+++ b/examples/security-tomcat-user-identitystore/README.adoc
@@ -0,0 +1,172 @@
+:index-group: Jakarta Security
+:jbake-type: page
+:jbake-status: status=published
+= Jakarta Security with Tomcat tomcat-users.xml identity store
+
+TomEE has its own independent Jakarta Security implementation https://eclipse-ee4j.github.io/security-api/ .
+
+[NOTE]
+====
+Jakarta Security defines a standard for creating secure Jakarta EE applications in modern application paradigms. It defines an overarching (end-user targeted) Security API for Jakarta EE Applications.
+
+Jakarta Security builds on the lower level Security SPIs defined by Jakarta Authentication and Jakarta Authorization, which are both not end-end targeted.
+====
+
+This example focuses in showing how to leverage Jakarta Security in TomEE with Tomcat's tomcat-users.xml.
+TomEE out of the box supports it as an identity store.
+
+== Implement a simple JAX-RS application
+
+This movie example has 2 resources, one of them `MovieAdminResource` is a protected resource to ensure only admin users can add or delete movies.
+
+[source,xml]
+----
+<web-app
+ 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"
+ version="3.1"
+>
+
+ <!-- Security constraints -->
+
+ <security-constraint>
+ <web-resource-collection>
+ <web-resource-name>Protected admin resource/url</web-resource-name>
+ <url-pattern>/api/movies/*</url-pattern>
+ <http-method-omission>GET</http-method-omission>
+ </web-resource-collection>
+ <auth-constraint>
+ <role-name>admin</role-name>
+ </auth-constraint>
+ </security-constraint>
+
+</web-app>
+----
+
+== Defining identity store and authentication mechanism
+
+Jakarta Security requires 2 things to authenticate a user
+
+* the identity store (aka `tomcat-users.xml` in this case): this is basically where users are stored with their user
+name, password, and the roles
+* the authentication mechanism: how the credentials are passed in.
+
+In this example, we want to use `tomcat-users.xml` identity store and basic authentication.
+We can define that in the resource itself using 2 annotations
+
+[source,java]
+----
+@Path("/movies")
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+@TomcatUserIdentityStoreDefinition
+@BasicAuthenticationMechanismDefinition
+@ApplicationScoped
+public class MovieAdminResource {
+
+ private static final Logger LOGGER = Logger.getLogger(MovieAdminResource.class.getName());
+
+ @Inject
+ private MovieStore store;
+
+ // JAXRS security context also wired with Jakarta Security
+ @Context
+ private javax.ws.rs.core.SecurityContext securityContext;
+
+ @POST
+ public Movie addMovie(final Movie newMovie) {
+ LOGGER.info(getUserName() + " adding new movie " + newMovie);
+ return store.addMovie(newMovie);
+ }
+
+ // See source file for full content
+
+ private String getUserName() {
+ if (securityContext.getUserPrincipal() != null) {
+ return String.format("%s[admin=%s]",
+ securityContext.getUserPrincipal().getName(),
+ securityContext.isUserInRole("admin"));
+ }
+
+ return null;
+ }
+
+}
+----
+
+IMPORTANT:
+====
+In TomEE, Jakarta Security is wired in all layers, you can use
+
+* `javax.ws.rs.core.SecurityContext#getUserPrincipal` and `isUserInRole` to get the User Principal and check if the user has a given role
+* `javax.security.enterprise.SecurityContext#getCallerPrincipal` and `isCallerInRole` to get the Caller Principal (notice the difference in terms of naming) and check if a caller has a given role
+* `javax.servlet.http.HttpServletRequest#getUserPrincipal` and `isUserInRole`
+* `javax.ejb.SessionContext#getCallerPrincipal` and `isCallerInRole`
+* the `Subject` from the `PolicyContext` but this is less used
+====
+
+A lot of different APIs to retrieve the principal and check whereas it has a given role.
+It's all wired in and consistent in TomEE. No special configuration is needed.
+
+Finally, `MovieResource` does not require any authentication or user permissions, but for logging purposes in this test, it will use the Jakarta Security `SecurityContext` to grab the caller principal and do some role checks.
+
+== Add users to the regular `tomcat-users.xml`
+
+The file location is by default `${catalina.base}/conf`.
+The file can be located anywhere.
+If you are not using the default location, make sure to update the `server.xml` accordingly.
+
+[source,xml]
+----
+<tomcat-users>
+ <user name="tomcat" password="tomcat" roles="tomcat"/>
+ <user name="user" password="user" roles="user"/>
+
+ <user name="tom" password="secret1" roles="admin,manager"/>
+ <user name="emma" password="secret2" roles="admin,employee"/>
+ <user name="bob" password="secret3" roles="admin"/>
+</tomcat-users>
+----
+
+== Running
+
+Were we to run the above Main class or Test Case we'd see output like the following:
+
+[source,bash]
+----
+INFOS: Service URI: http://localhost:56147/api/movies -> Pojo org.superbiz.movie.MovieAdminResource
+juin 15, 2021 3:48:32 PM org.apache.openejb.server.cxf.rs.CxfRsHttpListener logEndpoints
+INFOS: DELETE http://localhost:56147/api/movies/{id} -> Movie deleteMovie(int)
+juin 15, 2021 3:48:32 PM org.apache.openejb.server.cxf.rs.CxfRsHttpListener logEndpoints
+INFOS: POST http://localhost:56147/api/movies -> Movie addMovie(Movie)
+juin 15, 2021 3:48:32 PM org.apache.openejb.server.cxf.rs.CxfRsHttpListener logEndpoints
+INFOS: Service URI: http://localhost:56147/api/movies -> Pojo org.superbiz.movie.MovieResource
+juin 15, 2021 3:48:32 PM org.apache.openejb.server.cxf.rs.CxfRsHttpListener logEndpoints
+INFOS: GET http://localhost:56147/api/movies -> List<Movie> getAllMovies()
+juin 15, 2021 3:48:32 PM org.apache.openejb.server.cxf.rs.CxfRsHttpListener logEndpoints
+INFOS: GET http://localhost:56147/api/movies/{id} -> Movie getMovie(int)
+juin 15, 2021 3:48:32 PM org.apache.openejb.server.cxf.rs.CxfRsHttpListener logEndpoints
+INFOS: Service URI: http://localhost:56147/api/openapi -> Pojo org.apache.geronimo.microprofile.openapi.jaxrs.OpenAPIEndpoint
+juin 15, 2021 3:48:32 PM org.apache.openejb.server.cxf.rs.CxfRsHttpListener logEndpoints
+INFOS: GET http://localhost:56147/api/openapi -> OpenAPI get()
+juin 15, 2021 3:48:32 PM sun.reflect.DelegatingMethodAccessorImpl invoke
+INFOS: Deployment of web application directory [/private/var/folders/03/fjcmr3cs2rnbtfcqd9w1nntc0000gn/T/temp2373416631427015263dir/apache-tomee/webapps/ROOT] has finished in [15,655] ms
+juin 15, 2021 3:48:32 PM sun.reflect.DelegatingMethodAccessorImpl invoke
+INFOS: Starting ProtocolHandler ["http-nio-56147"]
+juin 15, 2021 3:48:32 PM sun.reflect.DelegatingMethodAccessorImpl invoke
+INFOS: Server startup in [15904] milliseconds
+juin 15, 2021 3:48:32 PM sun.reflect.DelegatingMethodAccessorImpl invoke
+INFOS: Full bootstrap in [22621] milliseconds
+juin 15, 2021 3:48:33 PM org.superbiz.movie.MovieAdminResource addMovie
+INFOS: tom[admin=true] adding new movie Movie{title='Shanghai Noon', director='Tom Dey', genre='Comedy', id=7, year=2000}
+juin 15, 2021 3:48:34 PM org.superbiz.movie.MovieResource getAllMovies
+INFOS: tomcat[admin=false] reading movies
+juin 15, 2021 3:48:34 PM org.superbiz.movie.MovieResource getAllMovies
+INFOS: null reading movies
+juin 15, 2021 3:48:34 PM org.superbiz.movie.MovieResource getAllMovies
+INFOS: emma[admin=true] reading movies
+juin 15, 2021 3:48:34 PM org.superbiz.movie.MovieResource getMovie
+INFOS: bob[admin=true] reading movie 2 / Movie{title='Starsky & Hutch', director='Todd Phillips', genre='Action', id=2, year=2004}
+
+----
diff --git a/examples/security-tomcat-user-identitystore/pom.xml b/examples/security-tomcat-user-identitystore/pom.xml
new file mode 100644
index 0000000..4206f47
--- /dev/null
+++ b/examples/security-tomcat-user-identitystore/pom.xml
@@ -0,0 +1,74 @@
+<?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">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.superbiz</groupId>
+ <artifactId>serverless-tomee-microprofile</artifactId>
+ <version>8.0.8-SNAPSHOT</version>
+
+ <name>TomEE :: Examples :: Jakarta Security tomcat-user.xml Identity Store</name>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <version.tomee>8.0.8-SNAPSHOT</version.tomee>
+ </properties>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.tomee.bom</groupId>
+ <artifactId>tomee-microprofile</artifactId>
+ <version>${version.tomee}</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.13.1</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>3.5.1</version>
+ <configuration>
+ <source>1.8</source>
+ <target>1.8</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <!--
+ This section allows you to configure where to publish libraries for sharing.
+ It is not required and may be deleted. For more information see:
+ http://maven.apache.org/plugins/maven-deploy-plugin/
+ -->
+ <distributionManagement>
+ <repository>
+ <id>localhost</id>
+ <url>file://${basedir}/target/repo/</url>
+ </repository>
+ <snapshotRepository>
+ <id>localhost</id>
+ <url>file://${basedir}/target/snapshot-repo/</url>
+ </snapshotRepository>
+ </distributionManagement>
+</project>
diff --git a/examples/security-tomcat-user-identitystore/src/main/java/org/superbiz/movie/Api.java b/examples/security-tomcat-user-identitystore/src/main/java/org/superbiz/movie/Api.java
new file mode 100644
index 0000000..6826022
--- /dev/null
+++ b/examples/security-tomcat-user-identitystore/src/main/java/org/superbiz/movie/Api.java
@@ -0,0 +1,26 @@
+/*
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.superbiz.movie;
+
+import javax.annotation.security.DeclareRoles;
+import javax.ws.rs.ApplicationPath;
+import javax.ws.rs.core.Application;
+
+@ApplicationPath("/api")
+@DeclareRoles({"admin"})
+public class Api extends Application {
+}
diff --git a/examples/serverless-tomee-microprofile/src/main/java/org/superbiz/movie/wp/Movie.java b/examples/security-tomcat-user-identitystore/src/main/java/org/superbiz/movie/Movie.java
similarity index 85%
rename from examples/serverless-tomee-microprofile/src/main/java/org/superbiz/movie/wp/Movie.java
rename to examples/security-tomcat-user-identitystore/src/main/java/org/superbiz/movie/Movie.java
index b5b02df..774c306 100644
--- a/examples/serverless-tomee-microprofile/src/main/java/org/superbiz/movie/wp/Movie.java
+++ b/examples/security-tomcat-user-identitystore/src/main/java/org/superbiz/movie/Movie.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.superbiz.movie.wp;
+package org.superbiz.movie;
public class Movie {
@@ -74,4 +74,14 @@ public class Movie {
public void setYear(final int year) {
this.year = year;
}
+
+ @Override public String toString() {
+ return "Movie{" +
+ "title='" + title + '\'' +
+ ", director='" + director + '\'' +
+ ", genre='" + genre + '\'' +
+ ", id=" + id +
+ ", year=" + year +
+ '}';
+ }
}
diff --git a/examples/security-tomcat-user-identitystore/src/main/java/org/superbiz/movie/MovieAdminResource.java b/examples/security-tomcat-user-identitystore/src/main/java/org/superbiz/movie/MovieAdminResource.java
new file mode 100644
index 0000000..cbe2f24
--- /dev/null
+++ b/examples/security-tomcat-user-identitystore/src/main/java/org/superbiz/movie/MovieAdminResource.java
@@ -0,0 +1,76 @@
+/*
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.superbiz.movie;
+
+import org.apache.tomee.security.cdi.TomcatUserIdentityStoreDefinition;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Inject;
+import javax.security.enterprise.authentication.mechanism.http.BasicAuthenticationMechanismDefinition;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.SecurityContext;
+import java.util.logging.Logger;
+
+@Path("/movies")
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+@TomcatUserIdentityStoreDefinition
+@BasicAuthenticationMechanismDefinition
+@ApplicationScoped
+public class MovieAdminResource {
+
+ private static final Logger LOGGER = Logger.getLogger(MovieAdminResource.class.getName());
+
+ @Inject
+ private MovieStore store;
+
+ // JAXRS security context also wired
+ @Context
+ private SecurityContext securityContext;
+
+ @POST
+ public Movie addMovie(final Movie newMovie) {
+ LOGGER.info(getUserName() + " adding new movie " + newMovie);
+ return store.addMovie(newMovie);
+ }
+
+ @DELETE
+ @Path("{id}")
+ public Movie deleteMovie(@PathParam("id") final int id) {
+ final Movie movie = store.deleteMovie(id);
+ LOGGER.info(getUserName() + " deleting movie " + id + " / " + movie);
+ return movie;
+ }
+
+ private String getUserName() {
+ if (securityContext.getUserPrincipal() != null) {
+ return String.format("%s[admin=%s]",
+ securityContext.getUserPrincipal().getName(),
+ securityContext.isUserInRole("admin"));
+ }
+
+ return null;
+ }
+
+}
diff --git a/examples/security-tomcat-user-identitystore/src/main/java/org/superbiz/movie/MovieResource.java b/examples/security-tomcat-user-identitystore/src/main/java/org/superbiz/movie/MovieResource.java
new file mode 100644
index 0000000..73793f0
--- /dev/null
+++ b/examples/security-tomcat-user-identitystore/src/main/java/org/superbiz/movie/MovieResource.java
@@ -0,0 +1,69 @@
+/*
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.superbiz.movie;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Inject;
+import javax.security.enterprise.SecurityContext;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import java.util.List;
+import java.util.logging.Logger;
+
+@Path("/movies")
+@Produces(MediaType.APPLICATION_JSON)
+@Consumes(MediaType.APPLICATION_JSON)
+@ApplicationScoped
+public class MovieResource {
+
+ private static final Logger LOGGER = Logger.getLogger(MovieResource.class.getName());
+
+ @Inject
+ private MovieStore store;
+
+ // jakarta enterprise security context
+ @Inject
+ private SecurityContext securityContext;
+
+ @GET
+ public List<Movie> getAllMovies() {
+ LOGGER.info(getCallerName() + " reading movies");
+ return store.getAllMovies();
+ }
+
+ @GET
+ @Path("{id}")
+ public Movie getMovie(@PathParam("id") final int id) {
+ final Movie movie = store.getMovie(id);
+ LOGGER.info(getCallerName() + " reading movie " + id + " / " + movie);
+ return movie;
+ }
+
+ private String getCallerName() {
+ if (securityContext.getCallerPrincipal() != null) {
+ return String.format("%s[admin=%s]",
+ securityContext.getCallerPrincipal().getName(),
+ securityContext.isCallerInRole("admin"));
+ }
+
+ return null;
+ }
+}
diff --git a/examples/security-tomcat-user-identitystore/src/main/java/org/superbiz/movie/MovieStore.java b/examples/security-tomcat-user-identitystore/src/main/java/org/superbiz/movie/MovieStore.java
new file mode 100644
index 0000000..5430474
--- /dev/null
+++ b/examples/security-tomcat-user-identitystore/src/main/java/org/superbiz/movie/MovieStore.java
@@ -0,0 +1,63 @@
+/*
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.superbiz.movie;
+
+import javax.annotation.PostConstruct;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.context.RequestScoped;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+// request scoped is totally desired in this simple test
+// the goal is for each request we make to have always the same piece of data
+// it makes the assertions easier and not dependant on the order of the execution
+@RequestScoped
+public class MovieStore {
+
+ // not really required to have a concurrent map because it's request scoped bean
+ private final ConcurrentMap<Integer, Movie> store = new ConcurrentHashMap<>();
+
+ @PostConstruct
+ public void construct(){
+ this.addMovie(new Movie("Wedding Crashers", "David Dobkin", "Comedy", 1, 2005));
+ this.addMovie(new Movie("Starsky & Hutch", "Todd Phillips", "Action", 2, 2004));
+ this.addMovie(new Movie("Shanghai Knights", "David Dobkin", "Action", 3, 2003));
+ this.addMovie(new Movie("I-Spy", "Betty Thomas", "Adventure", 4, 2002));
+ this.addMovie(new Movie("The Royal Tenenbaums", "Wes Anderson", "Comedy", 5, 2001));
+ this.addMovie(new Movie("Zoolander", "Ben Stiller", "Comedy", 6, 2001));
+ }
+
+ public List<Movie> getAllMovies() {
+ return new ArrayList<>(store.values());
+ }
+
+ public Movie addMovie(final Movie newMovie) {
+ store.putIfAbsent(newMovie.getId(), newMovie);
+ return newMovie;
+ }
+
+ public Movie deleteMovie(final int id) {
+ return store.remove(id);
+ }
+
+ public Movie getMovie(final int id) {
+ return store.get(id);
+ }
+
+}
diff --git a/examples/security-tomcat-user-identitystore/src/main/resources/META-INF/beans.xml b/examples/security-tomcat-user-identitystore/src/main/resources/META-INF/beans.xml
new file mode 100644
index 0000000..b240367
--- /dev/null
+++ b/examples/security-tomcat-user-identitystore/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,18 @@
+<?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.
+-->
+<beans/>
diff --git a/examples/security-tomcat-user-identitystore/src/main/resources/conf/tomcat-users.xml b/examples/security-tomcat-user-identitystore/src/main/resources/conf/tomcat-users.xml
new file mode 100644
index 0000000..eaf108a
--- /dev/null
+++ b/examples/security-tomcat-user-identitystore/src/main/resources/conf/tomcat-users.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ 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.
+ -->
+<tomcat-users>
+ <user name="tomcat" password="tomcat" roles="tomcat"/>
+ <user name="user" password="user" roles="user"/>
+
+ <user name="tom" password="secret1" roles="admin,manager"/>
+ <user name="emma" password="secret2" roles="admin,employee"/>
+ <user name="bob" password="secret3" roles="admin"/>
+</tomcat-users>
diff --git a/examples/security-tomcat-user-identitystore/src/main/webapp/WEB-INF/web.xml b/examples/security-tomcat-user-identitystore/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..a2f32af
--- /dev/null
+++ b/examples/security-tomcat-user-identitystore/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (c) 2015, 2020 Oracle and/or its affiliates. All rights reserved.
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Public License v. 2.0, which is available at
+ http://www.eclipse.org/legal/epl-2.0.
+ This Source Code may also be made available under the following Secondary
+ Licenses when the conditions for such availability set forth in the
+ Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ version 2 with the GNU Classpath Exception, which is available at
+ https://www.gnu.org/software/classpath/license.html.
+ SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+-->
+
+<web-app
+ 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"
+ version="3.1"
+>
+
+ <!-- Security constraints -->
+
+ <security-constraint>
+ <web-resource-collection>
+ <web-resource-name>Protected admin resource/url</web-resource-name>
+ <url-pattern>/api/movies/*</url-pattern>
+ <http-method-omission>GET</http-method-omission>
+ </web-resource-collection>
+ <auth-constraint>
+ <role-name>admin</role-name>
+ </auth-constraint>
+ </security-constraint>
+
+</web-app>
\ No newline at end of file
diff --git a/examples/security-tomcat-user-identitystore/src/test/java/org/superbiz/movie/BasicAuthFilter.java b/examples/security-tomcat-user-identitystore/src/test/java/org/superbiz/movie/BasicAuthFilter.java
new file mode 100644
index 0000000..91d009d
--- /dev/null
+++ b/examples/security-tomcat-user-identitystore/src/test/java/org/superbiz/movie/BasicAuthFilter.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.superbiz.movie;
+
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientRequestFilter;
+import java.io.IOException;
+import java.util.Base64;
+
+import static javax.ws.rs.core.HttpHeaders.AUTHORIZATION;
+
+public class BasicAuthFilter implements ClientRequestFilter {
+ private final String username;
+ private final String password;
+
+ public BasicAuthFilter(final String username, final String password) {
+ this.username = username;
+ this.password = password;
+ }
+
+ @Override
+ public void filter(final ClientRequestContext requestContext) throws IOException {
+ requestContext.getHeaders()
+ .add(AUTHORIZATION,
+ "Basic " + new String(Base64.getEncoder().encode((username + ":" + password).getBytes())));
+ }
+}
diff --git a/examples/security-tomcat-user-identitystore/src/test/java/org/superbiz/movie/MovieResourceTest.java b/examples/security-tomcat-user-identitystore/src/test/java/org/superbiz/movie/MovieResourceTest.java
new file mode 100644
index 0000000..c1d21e5
--- /dev/null
+++ b/examples/security-tomcat-user-identitystore/src/test/java/org/superbiz/movie/MovieResourceTest.java
@@ -0,0 +1,170 @@
+/*
+ * 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.superbiz.movie;
+
+import org.apache.tomee.bootstrap.Archive;
+import org.apache.tomee.bootstrap.Server;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import java.io.File;
+import java.net.URI;
+
+import static javax.ws.rs.client.Entity.entity;
+import static org.junit.Assert.assertEquals;
+
+public class MovieResourceTest {
+
+ private static URI serverURI;
+
+ @BeforeClass
+ public static void setup() {
+ // Add any classes you need to an Archive
+ // or add them to a jar via any means
+ final Archive classes = Archive.archive()
+ .add(Api.class)
+ .add(Movie.class)
+ .add(MovieStore.class)
+ .add(MovieResource.class)
+ .add(MovieAdminResource.class);
+
+ // Place the classes where you would want
+ // them in a Tomcat install
+ final Server server = Server.builder()
+ // This effectively creates a webapp called ROOT
+ .add("webapps/ROOT/WEB-INF/classes", classes)
+ .add("webapps/ROOT/WEB-INF/web.xml", new File("src/main/webapp/WEB-INF/web.xml"))
+ .add("conf/tomcat-users.xml", new File("src/main/resources/conf/tomcat-users.xml"))
+ .build();
+
+ serverURI = server.getURI();
+ }
+
+ @Test
+ public void getAllMovies() {
+ final WebTarget target = ClientBuilder.newClient().target(serverURI);
+
+ final Movie[] movies = target.path("/api/movies").request().get(Movie[].class);
+
+ assertEquals(6, movies.length);
+
+ final Movie movie = movies[1];
+ assertEquals("Todd Phillips", movie.getDirector());
+ assertEquals("Starsky & Hutch", movie.getTitle());
+ assertEquals("Action", movie.getGenre());
+ assertEquals(2004, movie.getYear());
+ assertEquals(2, movie.getId());
+ }
+
+ @Test
+ public void getAllMoviesAuthenticated() {
+ final WebTarget target = ClientBuilder.newClient()
+ .target(serverURI)
+ .register(new BasicAuthFilter("tomcat", "tomcat"));
+
+ final Movie[] movies = target.path("/api/movies").request().get(Movie[].class);
+
+ assertEquals(6, movies.length);
+
+ final Movie movie = movies[1];
+ assertEquals("Todd Phillips", movie.getDirector());
+ assertEquals("Starsky & Hutch", movie.getTitle());
+ assertEquals("Action", movie.getGenre());
+ assertEquals(2004, movie.getYear());
+ assertEquals(2, movie.getId());
+ }
+
+ @Test
+ public void getMovieAuthenticated() {
+ final WebTarget target = ClientBuilder.newClient()
+ .target(serverURI)
+ .register(new BasicAuthFilter("bob", "secret3"));
+
+ final Movie movie = target.path("/api/movies/2").request().get(Movie.class);
+
+ assertEquals("Todd Phillips", movie.getDirector());
+ assertEquals("Starsky & Hutch", movie.getTitle());
+ assertEquals("Action", movie.getGenre());
+ assertEquals(2004, movie.getYear());
+ assertEquals(2, movie.getId());
+ }
+
+ @Test
+ public void getAllMoviesEmma() {
+ final WebTarget target = ClientBuilder.newClient()
+ .target(serverURI)
+ .register(new BasicAuthFilter("emma", "secret2"));
+
+ final Movie[] movies = target.path("/api/movies").request().get(Movie[].class);
+
+ assertEquals(6, movies.length);
+
+ final Movie movie = movies[1];
+ assertEquals("Todd Phillips", movie.getDirector());
+ assertEquals("Starsky & Hutch", movie.getTitle());
+ assertEquals("Action", movie.getGenre());
+ assertEquals(2004, movie.getYear());
+ assertEquals(2, movie.getId());
+ }
+
+ @Test
+ public void addMovieAdmin() {
+ final WebTarget target = ClientBuilder.newClient()
+ .target(serverURI)
+ .register(new BasicAuthFilter("tom", "secret1"));
+
+ final Movie movie = new Movie("Shanghai Noon", "Tom Dey", "Comedy", 7, 2000);
+
+ final Movie posted = target.path("/api/movies").request()
+ .post(entity(movie, MediaType.APPLICATION_JSON))
+ .readEntity(Movie.class);
+
+ assertEquals("Tom Dey", posted.getDirector());
+ assertEquals("Shanghai Noon", posted.getTitle());
+ assertEquals("Comedy", posted.getGenre());
+ assertEquals(2000, posted.getYear());
+ assertEquals(7, posted.getId());
+ }
+
+ @Test
+ public void addMovieNotAuthenticated() {
+ final WebTarget target = ClientBuilder.newClient()
+ .target(serverURI);
+
+ final Movie movie = new Movie("Shanghai Noon", "Tom Dey", "Comedy", 7, 2000);
+
+ assertEquals(401, target.path("/api/movies").request()
+ .post(entity(movie, MediaType.APPLICATION_JSON)).getStatus());
+
+ }
+
+ @Test
+ public void addMovieWrongPermission() {
+ final WebTarget target = ClientBuilder.newClient()
+ .target(serverURI)
+ .register(new BasicAuthFilter("tomcat", "tomcat"));
+
+ final Movie movie = new Movie("Shanghai Noon", "Tom Dey", "Comedy", 7, 2000);
+
+ assertEquals(403, target.path("/api/movies").request()
+ .post(entity(movie, MediaType.APPLICATION_JSON)).getStatus());
+
+ }
+}