Use Jakarta EE Identity Store With Payara and RDBMS
Originally published on 18 Oct 2022
Last updated on 26 Apr 2023
In my previous blog post,here,I examined the JSR-375 specifications and their implementation by Jakarta EE and, more specifically, by the Payara Platform (Server and Micro).
I presented a brief overview of the JSR-375 specifications and their new features, focusing on the notion of Identity Store and illustrating, with a simple example, one of the most common use cases: the LDAP (Lightweight Directory Access Protocol) based authentication and authorization process.
While using LDAP for this purpose is probably the most recommended way to manage Identity Stores, many organizations prefer using RDBMS (Relational Database Management Server) due, in most cases, to historical and cultural reasons. For these organizations, I'm presenting here a new use case which, despite its apparent simplicity, fully covers all the possible difficulties and traps that might be experienced by the software developer.
The Database Identity Store
The authentication and authorization process performed by any enterprise grade application wasn't discovered byJakarta EE, but has existed since the dawn of time! What Jakarta EE and its JSR-375 specs bring, as a new added value, is its standardization. There are dozens of Java security frameworks, each one coming with its own authentication and authorization mechanisms. Compared to Jakarta EE, these other security frameworks are neither better nor worse, they are just not standard. And that is the tremendous benefit of Jakarta EE: identifying common patterns and providing standard solutions, achieving the consensus of the majority of the software editors and working across the majority of platforms.
The Project
In order to illustrate the use of the database Identity Store, I'll be using the same project as the one I presented previously, to illustrate the LDAP Identity Store. It may be foundhere.
For the readers who didn't have the chance to see ourprevious post, I'll recall that this is an aggregate project, having the following structure:
-
an aggregator POM called
jsr-375
-
a WAR artifact, called
servlet-with-ldap-identity-store
, demonstrating the LDAP Identity Store -
a WAR artifact, called
servlet-with-jdbc-identity-store
, demonstrating the database Identity Store -
an infrastructure project, called
platform
, which relays ontestcontainers
in order to run two instances of Payara Platform, a Server and a Micro. Each one having deployed to it the two WARs referenced above
The Module servlet-with-jdbc-identity-store
This module, which is the one that interests us here, is organized around the following classes:
JdbcIdentityStoreConfig
: this is the configuration classJdbcIdentitySoreServlet
: this is a servlet demonstrating the database identity stored based authenticationJdbcSetup
: this class is setting up the Identity Store required schema.
Let's have a more detailed view of each of these classes.
The Class JdbcIdentityStoreConfig
The listing below shows an excerpt of the code:
@ApplicationScoped
@BasicAuthenticationMechanismDefinition(realmName="admin-realm")
@DatabaseIdentityStoreDefinition(
dataSourceLookup = "${'java:global/H2'}",
callerQuery = "select password from caller where name = ?",
groupsQuery = "select group_name from caller_groups where caller_name = ?"
)
public class JdbcIdentityStoreConfig{}
As we can see, our class is a CDI (Context and Dependency Injection) bean, having the application scoped. The annotation @DatabaseIdentityStoreDefinition
is the new Jakarta EE one, defining the database Identity Store mechanism. The argument named dataSourceLookup
declares a JNDI (Java Name and Directory Interface) lookup name which will bring the associated data source definition. Once this data source reference is found, we'll execute the two defined SQL queries, callerQuery
and groupsQuery
, in order to find the caller credentials, i.e. its identifier and password, as well as its group membership. The notion of caller here is somehow equivalent to the one of user, while being a less human connotation, as it could also be a service. Hence the pronoun "it".
But the most interesting thing to be noticed is the fact that we're using here the HTTP basic authentication mechanism, defined by the @BasicAuthenticationMechanismDefinition
annotation. Which means that, at the application startup, we'll be presented with a login screen and challenged to authenticate with a user name and password. This information will be further transmitted to the database Identity Store mechanism which will compare them with the ones stored in the database. This way we're composing two JSR-375 security features: the HTTP basic authentication associated to the database Identity Store. I'm leaving to the sovereign appraisal of the reader this facility which saves several dozens of lines of code.
The Class JdbcIdentityStoreServlet
This class is the same as the one used to demonstrate the LDAP Identity Store. It's a servlet to which access is authorized to any caller having the role of admin-role
. This is defined through the Jakarta EE annotation @ServletSecurity
. Another specific Jakarta EE annotation is @DeclareRoles
which allows for the enumeration of all the possible roles that the application should be aware of.
The Class JdbcSetup
This class is responsible for the creation and the initialization of the data-model required by the database Identity Store mechanism. In its @PostConstruct
method, it creates two database tables named caller
and, respectively, caller_groups
. Then, these tables are initialized with caller names, passwords and group names. The caller named admin
is then attached to the groups admin-role
and user-role
while the caller named user
is only a member of the group user-roles
.
It should be noted that the password is stored in the database as hashed. The JSR-375 specifications define the interface Pbkdf2PasswordHash
having a default implementation based on the PBKDF2WithHmacSHA256 algorithm. This implementation can be simply injected, as you can see in the associated source code. Here we are using the default implementation which is largely satisfactory for our example. But other more secure hash algorithms may be used as well and, in this case, the Pbkdf2PasswordHash
default implementation may be initialized by passing to it a map containing the algorithm name as well as parameters like the salt, the number of iterations, etc. The Jakarta EE documentation presents all these details in extenso.
Another thing to mention is the fact that, using Java JDBC (Java Data Base Connectivity) code to initialize the database in a runtime singleton @PostConstruct
method, is probably not the most elegant way to deal with SQL. The in-memory H2 database used here accepts, on its JDBC connection string, the argument named "run script from" allowing us to define the script to be run in order to initialize the database. Accordingly, instead of doing that in Java JDBC code and having to provide a dedicated EJB (Enterprise JavaBeans) for this purpose, we could have had an initialization SQL script to be ran automatically at the deployment time. And in order to deal with the password hashing, we could have dealt with the HASH
function that H2 provides in its more recent releases. However, the Payara Platform comes with an older release of the H2 database, which doesn't support this feature. Accordingly, to save ourselves the burden of having to upgrade the H2 database release which comes with the Payara Platform, we finally preferred this simpler alternative.
Running The Example
In order to run our example, proceed as follows:
- Execute the command
mvn clean install
. This command will stop the Docker containers, if they are running, and starts new instances. It also will run the integration test that should succeed. - The integration test already tested the service in a Docker container started withTestcontainers. But you can now test it on more production-ready containers, like the one managed by the platform Maven module. You can run commands like:
curl http://localhost:18080/servlet-with-jdbc-identity-store/secured -u "admin:passadmin"
to test on Payara Server or
curl http://localhost:28080/servlet-with-jdbc-identity-store/secured -u "admin:passadmin"
Enjoy !
Further reading:
Related Posts
A More Flexible Way to Deploy Jakarta EE Apps: Introducing Pay As You Go Pricing for Payara Cloud
Published on 05 Dec 2024
by Luqman Saeed
0 Comments
The Payara Monthly Catch - November 2024
Published on 28 Nov 2024
by Chiara Civardi
0 Comments