Using Jakarta Security on Tomcat and the Payara Platform
Java EE Security API is one of the new APIs in Java EE eight. With Java EE currently being transferred and rebranded to Jakarta EE, this API will soon be rebranded to Jakarta Security, which is the term we'll use in this article. Jakarta Security is part of the Jakarta APIs, included and active in the Payara Platform by default with no configuration required in order to use it. With some effort, Jakarta Security can be used with Tomcat, as well.
The API focuses on full standardization (not depending on proprietary server config) and recognizing the use case to configure security from within an application archive. The latter is specifically useful for cloud deployments, where lightweight and self-sufficient war archives can be deployed to generic Jakarta EE compatible containers.
Jakarta Security is included in the Payara Platform by default and is also active by default. As a result, there is nothing that must be included in a war archive to use Jakarta Security in the Payara Platform and nothing to configure in order for code to use it. Payara uses Soteria, which is the reference implementation under Java EE, but just one of the possible implementations under Jakarta, which did away with the RI concept. Another implementation is available from our friends at Open Liberty, and implementation from TomEE, aptly called TomEE Security is currently underway.
Soteria was designed with the usage of standard APIs in mind and the ability to run on different servers. Any functionality Soteria requires that isn't available in standard APIs is delegated to an SPI with a default implementation covering a series of well-known servers. This SPI is internal in Soteria 1.0.x, but it will be made public in Soteria 1.1.
Adding Soteria to Tomcat
Because Jakarta Security is very much based on Jakarta EE, a valid question to ask is: does it also run on Tomcat? The answer is yes, but with some caveats.
Jakarta Security depends on Servlet (with the Servlet Container Profile JASPIC extensions), Expression Language and CDI. Soteria additionally depends on JACC, but this is abstracted via its SPI:
Jakarta Security and Soteria
Tomcat obviously already implements Servlet (with the Servlet Container Profile JASPIC extensions), but it also implements Expression Language. To start getting it to run we first need to add CDI to Tomcat. This basically entails adding a CDI implementation, where Weld is quite easy to use by adding the following to pom.xml in a Maven project:
Subsequently, we also need to register the CDI bean manager in JNDI by adding a META-INF/context.xml file with the following contents:
Next, we'll add the Soteria implementation:
We don't necessarily have to include the respective API dependencies (CDI and Jakarta Security), as they are referenced by the implementations, and therefore, automatically fetched by Maven.
The above should be enough to get things running. (We'll come to the JACC dependency later.) We should look out for the path of the bean manager. Occasionally, the bean manager will get stored under "java:comp/env/BeanManager" instead of its normal path: "java:comp/BeanManager". This is a rather well-known trap, and many projects have adjusted for this, but Soteria hasn't yet. Soteria 1.1 will certainly include a fix for this.
In the meantime, what we can do is using a handy trick in Servlet, where we can override classes coming from embedded jars (jars in WEB-INF/lib). To do this, we copy the CdiUtils.java file from the Soteria source to a package with the same name in our project. Then, in that file, we change the getBeanManager() method to:
With this all in place we can now code up a simple protected Servlet, using the provided BASIC authentication mechanism:
What we are seeing here is a fairly standard Servlet, with the only extra bit the Jakarta Security annotation for defining the BasicAuthenticationMechanism. Note that while the annotation is put on the Servlet class in this example, it's a global annotation that sets the authentication mechanism for the entire application. Just setting the authentication mechanism from an application may not seem terribly exciting at first sight (one can do this using web.xml as well), but the main difference here is that the authentication mechanism is a CDI bean with a well-defined interface, which opens up lots of options regarding intercepting, decorating and replacing the bean.
Something which can't really be done with plain Servlets and only in a fairly obscure way with the Servlet Container Profile JASPIC extensions on Tomcat is defining the database within the application which is used to validate the credentials and return groups (which map one-to-one to roles by default). With Jakarta Security this is trivial:
Running this on Tomcat, we see the various bits and pieces being initialized and picked up in the server log:
Deploying and running this on Tomcat will present us with a well-known browser authentication dialog. If everything went right, we get the following output:
A fully working example is available in the Vendor EE Samples project.
Adding a JWT Authentication Mechanism
An important aspect of Jakarta Security vs. native Servlet security authentication mechanisms is that the same APIs that are used by the build-in mechanisms are available to build third-party mechanisms as well. For instance, the MicroProfile JWT authentication mechanism can be based on Jakarta Security. In fact, two implementations (Payara MP JWT and SmallRye MP JWT) do. This makes it possible to add those to Tomcat.
For this tutorial, we'll be looking at Payara MP JWT. Payara MP JWT, however, is currently not published on Maven central although it can be built from source by cloning and building it using mvn clean install. MP JWT does have its own additional dependencies, which in terms of APIs are JSON-P and MP Config. For Tomcat, we have to add implementations for those as well, and we'll do that by adding SmallRye Config and the GlassFish JSON-P implementation. This will make the dependencies we list in our pom.xml quite a bit longer; the implementations actually depend on the APIs, so we don't really need to list them (since Maven will fetch transitive dependencies). Our pom dependency section will then look as follows:
With this in place, we can start using MP JWT on Tomcat. As an example application, we will modify an existing MicroProfile sample to make it Tomcat compatible. Without repeating all the existing MP JWT tutorials here, in short using MP JWT means setting the authentication mechanism by placing the @LoginConfig annotation somewhere, and then configuring a public key inside the application that will be used to verify incoming JWT tokens. For the request we sign a JWT token with the corresponding private key and include it in an "authorization" header, for instance using wget:
wget --header="Authorization: BearereyJraWQiOiJcL3ByaXZhdGVLZXkucGVtIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiIyNDQwMDMyMCIsImF1ZCI6InM2QmhkUmtxdDMiLCJ1cG4iOiJ0ZXN0IiwiYXV0aF90aW1lIjoxNTYxNDYwOTEwLCJpc3MiOiJvcmcuZWNsaXBzZS5taWNyb3Byb2ZpbGUxMiIsImdyb3VwcyI6WyJhcmNoaXRlY3QiLCJtYXN0ZXIiLCJsZWFkZXIiLCJkZXYiXSwiZXhwIjoxNTcxNDYwOTEwLCJpYXQiOjE1NjE0NjA5MTAsImp0aSI6ImEtMTIzIn0.RLxhWgjyMN39W-3Ls-ppqAJvOeeaUmb4Y8NXKbhjufdAjgkA6x8OhAURQK22z1rBnMM5HMuRWF9uIEbgA7I4A5kdLfiDidEFZsIYFEJEzKBjDHN8Ind5kWY63CppAnTYhJYZ7oN2yfJ7wjfRQLllTBUY59YiZM-yuMEkOhgC9Tk6EpG1Xf390EmhoS7w8DokN89Q5ANrZtcFpIrOQChq-RW60QeKfk13xgfgD1hOqwy3C6K5gWSfP1ceHcoFrtRRqE5vKZmnpxaB82vQZFKJMg7E-iRm9eqtqN4G0ZfMutv0wP2v6SgSkuez_5tj17DHKVYF3dOZXlWfzST_VQw7JQ" http://localhost:8080/jwt/servlet
If everything went as planned, we will then get the following response:
This is a protected servlet
web username: test
web user has role "architect": true
web user has role "bar": false
web user has role "kaz": false
As we can see, this indeed works on Tomcat, attesting to the already quite modular nature of Jakarta EE and MicroProfile implementation components.
A fully working example is again available in the Vendor EE Samples project.
The JACC Dependency
Coming back to the missing JACC dependency, which we mentioned at the start of this article: it's used by Soteria to implement the Jakarta Security SecurityContext. Since JACC is mostly about exposing something that a Servlet container does in a standard way (essentially an index of security constraints), you can't really have a standalone implementation of it. The closest would be to manually parse web.xml and manually scan for the associated security annotations, build our own index, and implement the same resolving algorithm as Servlet containers are implementing. OmniFaces takes this approach for its WebXml class, but it's not ideal. The best solution would be for Tomcat to implement the Servlet extensions for JACC, for which some mild interest was shown before. Finally, the upcoming public Soteria SPI could be taken advantage of to use Tomcat's native APIs directly to access its internal security constraints.
With all of this not available for Tomcat there's no alternative at the moment, so we can't use the SecurityContext. Everything else works though.
Using Jakarta Security on the Payara Platform
To use Jakarta Security on the Payara Platform, nothing special is needed. It’s just there and ready to go- just like the Servlet API is just “there”, for example.
This means any book, tutorial, or example on Jakarta Security can be followed, and nothing Payara-specific has to be done by the user. The following are small examples of using Jakarta Security:
- Example apps in Soteria — https://github.com/eclipse-ee4j/soteria/tree/master/test (work on other Jakarta Security implementations too).
- Java EE 8 Samples — https://github.com/javaee-samples/javaee8-samples/tree/master/security.