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