| :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 |
| |
| * `jakarta.ws.rs.core.SecurityContext#getUserPrincipal` and `isUserInRole` to get the User Principal and check if the user has a given role |
| * `jakarta.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 |
| * `jakarta.servlet.http.HttpServletRequest#getUserPrincipal` and `isUserInRole` |
| * `jakarta.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>Description</b> The request has not been applied because it lacks valid authentication credentials for the target resource.</p><hr class="line" /><h3>Apache Tomcat (TomEE)/9.0.52 (10.0.0-SNAPSHOT)</h3></body></html> |
| |
| |
| 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>Description</b> The request has not been applied because it lacks valid authentication credentials for the target resource.</p><hr class="line" /><h3>Apache Tomcat (TomEE)/9.0.52 (10.0.0-SNAPSHOT)</h3></body></html> |
| |
| |
| 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> Access to the requested resource has been denied</p><p><b>Description</b> The server understood the request but refuses to authorize it.</p><hr class="line" /><h3>Apache Tomcat (TomEE)/9.0.52 (10.0.0-SNAPSHOT)</h3></body></html> |
| ---- |