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/24 13:01:19 UTC
[tomee] branch master updated: TOMEE-3761 Example for Jakarta
Security with a custom IdentityStore
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 9dcad5a TOMEE-3761 Example for Jakarta Security with a custom IdentityStore
9dcad5a is described below
commit 9dcad5a1bac2ed2115c81e86e38e25498c1c1a2a
Author: Jean-Louis Monteiro <jl...@tomitribe.com>
AuthorDate: Thu Jun 24 15:00:56 2021 +0200
TOMEE-3761 Example for Jakarta Security with a custom IdentityStore
---
examples/pom.xml | 1 +
examples/security-custom-identitystore/README.adoc | 142 +++++++++++++++++++++
examples/security-custom-identitystore/pom.xml | 82 ++++++++++++
.../java/org/superbiz/security/MovieServlet.java | 74 +++++++++++
.../org/superbiz/security/TestIdentityStore.java | 51 ++++++++
.../src/main/resources/META-INF/beans.xml | 18 +++
.../test/java/org/superbiz/security/MovieTest.java | 110 ++++++++++++++++
7 files changed, 478 insertions(+)
diff --git a/examples/pom.xml b/examples/pom.xml
index 6c7b962..4c21424 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -219,6 +219,7 @@
<!-- Jakarta Security Examples -->
<module>security-tomcat-user-identitystore</module>
+ <module>security-custom-identitystore</module>
</modules>
<dependencies>
diff --git a/examples/security-custom-identitystore/README.adoc b/examples/security-custom-identitystore/README.adoc
new file mode 100644
index 0000000..eb0dfe2
--- /dev/null
+++ b/examples/security-custom-identitystore/README.adoc
@@ -0,0 +1,142 @@
+:index-group: Jakarta Security
+:jbake-type: page
+:jbake-status: status=published
+= Jakarta Security with a custom 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.
+====
+
+As the specification requires, TomEE supports by default JDBC and JDAP identity stores. It also has a default support for Tomcat's 'tomcat-users.xml' (See security-tomcat-user-identitystore example).
+
+This example will show how you can leverage your own identity store to authenticate users.
+This is very often required for integrating your systems.
+
+== Implement a simple servlet
+
+This movie servlet, is a very simple example that defines a BasicAuthenticationMechanism, some roles and security constraints.
+
+[source,java]
+----
+@WebServlet("/movies")
+@DeclareRoles({"foo","bar","kaz"})
+@ServletSecurity(@HttpConstraint(rolesAllowed = "foo"))
+@BasicAuthenticationMechanismDefinition
+public class MovieServlet extends HttpServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void doGet(final HttpServletRequest request, final HttpServletResponse response)
+ throws ServletException, IOException {
+
+ String webName = null;
+ if (request.getUserPrincipal() != null) {
+ webName = request.getUserPrincipal().getName();
+ }
+
+ response.getWriter().write(
+ "<html><body> Welcome to Movie servlet <br><br>\n" +
+
+ "web username: " + webName + "<br><br>\n" +
+
+ "web user has role \"foo\": " + request.isUserInRole("foo") + "<br>\n" +
+ "web user has role \"bar\": " + request.isUserInRole("bar") + "<br>\n" +
+ "web user has role \"kaz\": " + request.isUserInRole("kaz") + "<br><br>");
+ }
+
+}
+----
+
+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.
+
+== Create your own IdentityStore implementation
+
+For the sake of keeping this example as simple as possible, the `TestIdentityStore` is very simple.
+
+It recognizes only 2 users and only one of them has the right roles to call the servlet.
+
+[source,java]
+----
+@ApplicationScoped
+public class TestIdentityStore implements IdentityStore {
+
+ public CredentialValidationResult validate(Credential credential) {
+
+ if (!(credential instanceof UsernamePasswordCredential)) {
+ return INVALID_RESULT;
+ }
+
+ final UsernamePasswordCredential usernamePasswordCredential = (UsernamePasswordCredential) credential;
+ if (usernamePasswordCredential.compareTo("jon", "doe")) {
+ return new CredentialValidationResult("jon", new HashSet<>(asList("foo", "bar")));
+ }
+
+ if (usernamePasswordCredential.compareTo("iron", "man")) {
+ return new CredentialValidationResult("iron", new HashSet<>(Collections.singletonList("avengers")));
+ }
+
+ return INVALID_RESULT;
+ }
+
+}
+----
+
+There is nothing else to configure or do.
+The identity store must implement the IdentityStore interface.
+It must be a CDI bean and then TomEE will pick it up automatically and delegate user authentication.
+
+== Running
+
+Were we to run the above Main class or Test Case we'd see output like the following:
+
+[source,bash]
+----
+....
+INFOS: Starting ProtocolHandler ["http-nio-54313"]
+juin 24, 2021 2:58:42 PM sun.reflect.DelegatingMethodAccessorImpl invoke
+INFOS: Server startup in [4703] milliseconds
+juin 24, 2021 2:58:42 PM sun.reflect.DelegatingMethodAccessorImpl invoke
+INFOS: Full bootstrap in [7638] milliseconds
+
+
+Calling MovieServlet without any credentials provided.
+juin 24, 2021 2:58:43 PM com.gargoylesoftware.htmlunit.WebClient printContentIfNecessary
+INFOS: statusCode=[401] contentType=[text/html]
+juin 24, 2021 2:58:43 PM com.gargoylesoftware.htmlunit.WebClient printContentIfNecessary
+INFOS: <!doctype html><html lang="en"><head><title>HTTP Status 401 – Unauthorized</title><style type="text/css">body {font-family:Tahoma,Arial,sans-serif;} h1, h2, h3, b {color:white;background-color:#525D76;} h1 {font-size:22px;} h2 {font-size:16px;} h3 {font-size:14px;} p {font-size:12px;} a {color:black;} .line {height:1px;background-color:#525D76;border:none;}</style></head><body><h1>HTTP Status 401 – Unauthorized</h1><hr class="line" /><p><b>Type</b> Status Report</p><p><b>Descripti [...]
+
+
+Calling MovieServlet with a valid user and valid permissions.
+
+
+Calling MovieServlet with the wrong credentials.
+juin 24, 2021 2:58:44 PM com.gargoylesoftware.htmlunit.WebClient printContentIfNecessary
+INFOS: statusCode=[401] contentType=[text/html]
+juin 24, 2021 2:58:44 PM com.gargoylesoftware.htmlunit.WebClient printContentIfNecessary
+INFOS: <!doctype html><html lang="en"><head><title>HTTP Status 401 – Unauthorized</title><style type="text/css">body {font-family:Tahoma,Arial,sans-serif;} h1, h2, h3, b {color:white;background-color:#525D76;} h1 {font-size:22px;} h2 {font-size:16px;} h3 {font-size:14px;} p {font-size:12px;} a {color:black;} .line {height:1px;background-color:#525D76;border:none;}</style></head><body><h1>HTTP Status 401 – Unauthorized</h1><hr class="line" /><p><b>Type</b> Status Report</p><p><b>Descripti [...]
+
+
+Calling MovieServlet with a valid user but without required permissions.
+juin 24, 2021 2:58:44 PM com.gargoylesoftware.htmlunit.WebClient printContentIfNecessary
+INFOS: statusCode=[403] contentType=[text/html]
+juin 24, 2021 2:58:44 PM com.gargoylesoftware.htmlunit.WebClient printContentIfNecessary
+INFOS: <!doctype html><html lang="en"><head><title>HTTP Status 403 – Forbidden</title><style type="text/css">body {font-family:Tahoma,Arial,sans-serif;} h1, h2, h3, b {color:white;background-color:#525D76;} h1 {font-size:22px;} h2 {font-size:16px;} h3 {font-size:14px;} p {font-size:12px;} a {color:black;} .line {height:1px;background-color:#525D76;border:none;}</style></head><body><h1>HTTP Status 403 – Forbidden</h1><hr class="line" /><p><b>Type</b> Status Report</p><p><b>Message</b> Acc [...]
+----
diff --git a/examples/security-custom-identitystore/pom.xml b/examples/security-custom-identitystore/pom.xml
new file mode 100644
index 0000000..bcd061c
--- /dev/null
+++ b/examples/security-custom-identitystore/pom.xml
@@ -0,0 +1,82 @@
+<?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>security-custom-identitystore</artifactId>
+ <version>8.0.8-SNAPSHOT</version>
+
+ <name>TomEE :: Examples :: Jakarta Security custom 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>
+ <dependency>
+ <groupId>net.sourceforge.htmlunit</groupId>
+ <artifactId>htmlunit</artifactId>
+ <version>2.50.0</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-custom-identitystore/src/main/java/org/superbiz/security/MovieServlet.java b/examples/security-custom-identitystore/src/main/java/org/superbiz/security/MovieServlet.java
new file mode 100644
index 0000000..4aebe9d
--- /dev/null
+++ b/examples/security-custom-identitystore/src/main/java/org/superbiz/security/MovieServlet.java
@@ -0,0 +1,74 @@
+/*
+ * 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.security;
+
+import javax.annotation.security.DeclareRoles;
+import javax.security.enterprise.authentication.mechanism.http.BasicAuthenticationMechanismDefinition;
+import javax.servlet.ServletException;
+import javax.servlet.annotation.HttpConstraint;
+import javax.servlet.annotation.ServletSecurity;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+@WebServlet("/movies")
+@DeclareRoles({"foo","bar","kaz"})
+@ServletSecurity(@HttpConstraint(rolesAllowed = "foo"))
+@BasicAuthenticationMechanismDefinition
+public class MovieServlet extends HttpServlet {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void doGet(final HttpServletRequest request, final HttpServletResponse response)
+ throws ServletException, IOException {
+
+ String webName = null;
+ if (request.getUserPrincipal() != null) {
+ webName = request.getUserPrincipal().getName();
+ }
+
+ response.getWriter().write(
+ "<html><body> Welcome to Movie servlet <br><br>\n" +
+
+ "web username: " + webName + "<br><br>\n" +
+
+ "web user has role \"foo\": " + request.isUserInRole("foo") + "<br>\n" +
+ "web user has role \"bar\": " + request.isUserInRole("bar") + "<br>\n" +
+ "web user has role \"kaz\": " + request.isUserInRole("kaz") + "<br><br>\n" +
+
+
+ "<form method=\"POST\">" +
+ "<input type=\"hidden\" name=\"logout\" value=\"true\" >" +
+ "<input type=\"submit\" value=\"Logout\">" +
+ "</form>" +
+ "</body></html>");
+ }
+
+ @Override
+ public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+ if ("true".equals(request.getParameter("logout"))) {
+ request.logout();
+ request.getSession().invalidate();
+ }
+
+ doGet(request, response);
+ }
+
+}
\ No newline at end of file
diff --git a/examples/security-custom-identitystore/src/main/java/org/superbiz/security/TestIdentityStore.java b/examples/security-custom-identitystore/src/main/java/org/superbiz/security/TestIdentityStore.java
new file mode 100644
index 0000000..0fd8bbd
--- /dev/null
+++ b/examples/security-custom-identitystore/src/main/java/org/superbiz/security/TestIdentityStore.java
@@ -0,0 +1,51 @@
+/*
+ * 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.security;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.security.enterprise.credential.Credential;
+import javax.security.enterprise.credential.UsernamePasswordCredential;
+import javax.security.enterprise.identitystore.CredentialValidationResult;
+import javax.security.enterprise.identitystore.IdentityStore;
+import java.util.Collections;
+import java.util.HashSet;
+
+import static java.util.Arrays.asList;
+import static javax.security.enterprise.identitystore.CredentialValidationResult.INVALID_RESULT;
+
+@ApplicationScoped
+public class TestIdentityStore implements IdentityStore {
+
+ public CredentialValidationResult validate(Credential credential) {
+
+ if (!(credential instanceof UsernamePasswordCredential)) {
+ return INVALID_RESULT;
+ }
+
+ final UsernamePasswordCredential usernamePasswordCredential = (UsernamePasswordCredential) credential;
+ if (usernamePasswordCredential.compareTo("jon", "doe")) {
+ return new CredentialValidationResult("jon", new HashSet<>(asList("foo", "bar")));
+ }
+
+ if (usernamePasswordCredential.compareTo("iron", "man")) {
+ return new CredentialValidationResult("iron", new HashSet<>(Collections.singletonList("avengers")));
+ }
+
+ return INVALID_RESULT;
+ }
+
+}
\ No newline at end of file
diff --git a/examples/security-custom-identitystore/src/main/resources/META-INF/beans.xml b/examples/security-custom-identitystore/src/main/resources/META-INF/beans.xml
new file mode 100644
index 0000000..b240367
--- /dev/null
+++ b/examples/security-custom-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-custom-identitystore/src/test/java/org/superbiz/security/MovieTest.java b/examples/security-custom-identitystore/src/test/java/org/superbiz/security/MovieTest.java
new file mode 100644
index 0000000..5d79b88
--- /dev/null
+++ b/examples/security-custom-identitystore/src/test/java/org/superbiz/security/MovieTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.security;
+
+import com.gargoylesoftware.htmlunit.DefaultCredentialsProvider;
+import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
+import com.gargoylesoftware.htmlunit.WebClient;
+import com.gargoylesoftware.htmlunit.html.HtmlPage;
+import org.apache.tomee.bootstrap.Archive;
+import org.apache.tomee.bootstrap.Server;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.net.URI;
+
+public class MovieTest {
+
+ 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(MovieServlet.class)
+ .add(TestIdentityStore.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/beans.xml", "")
+ .build();
+
+ serverURI = server.getURI();
+ }
+
+ @Test
+ public void getWithoutAuthentication() throws Exception {
+ System.out.println("\n\nCalling MovieServlet without any credentials provided.");
+ try (final WebClient webClient = new WebClient()) {
+ webClient.getPage(serverURI.toString() + "/movies");
+
+ } catch (final FailingHttpStatusCodeException e) {
+ Assert.assertEquals(401, e.getStatusCode());
+ }
+ }
+
+ @Test
+ public void getWrongUser() throws Exception {
+ System.out.println("\n\nCalling MovieServlet with the wrong credentials.");
+ try (final WebClient webClient = new WebClient()) {
+ //set proxy username and password
+ final DefaultCredentialsProvider credentialsProvider = (DefaultCredentialsProvider) webClient.getCredentialsProvider();
+ credentialsProvider.addCredentials("username", "password");
+
+ webClient.getPage(serverURI.toString() + "/movies");
+
+ } catch (final FailingHttpStatusCodeException e) {
+ Assert.assertEquals(401, e.getStatusCode());
+ }
+ }
+
+ @Test
+ public void getWrongPermission() throws Exception {
+ System.out.println("\n\nCalling MovieServlet with a valid user but without required permissions.");
+ try (final WebClient webClient = new WebClient()) {
+
+ //set proxy username and password
+ final DefaultCredentialsProvider credentialsProvider = (DefaultCredentialsProvider) webClient.getCredentialsProvider();
+ credentialsProvider.addCredentials("iron", "man");
+
+ webClient.getPage(serverURI.toString() + "/movies");
+
+ } catch (final FailingHttpStatusCodeException e) {
+ Assert.assertEquals(403, e.getStatusCode());
+ }
+ }
+
+ @Test
+ public void getRightPermissions() throws Exception {
+ System.out.println("\n\nCalling MovieServlet with a valid user and valid permissions.");
+ try (final WebClient webClient = new WebClient()) {
+ //set proxy username and password
+ final DefaultCredentialsProvider credentialsProvider = (DefaultCredentialsProvider) webClient.getCredentialsProvider();
+ credentialsProvider.addCredentials("jon", "doe");
+
+ // should not throw any exception now
+ final HtmlPage htmlPage = webClient.getPage(serverURI.toString() + "/movies");
+ Assert.assertTrue(htmlPage.asNormalizedText().contains("web username: jon"));
+ }
+ }
+
+}