Java EE Security API (JSR 375/Soteria) with JWT tokens
Originally published on 08 May 2018
Last updated on 11 Jul 2019
Introduction :
Java EE Security API (JSR 375) :
The Java EE Security API 1.0 is a new spec for Java EE 8 that aims to bridge some of the gaps that have traditionally been left unspecified and provides the new way to define or configure identity stores and authentication mechanisms.
Identity stores :
The Java EE Security API standardize identity store e.g DataBase, Embedded, LDAP etc and introduced a new standard interface for the custom identity store that is responsible for providing access to a storage system where caller data and credentials are stored.
@RequestScoped public class AuthenticationIdentityStore implements IdentityStore { @Override public CredentialValidationResult validate( Credential credential ) { return result; } }
Authentication mechanisms :
The authentication mechanism is responsible for interacting with the caller to obtain credentials. Just like with an identity store, an application can install an authentication mechanism by simply having an implementation of HttpAuthenticationMechanism interface.
@RequestScoped public class JWTAuthenticationMechanism implements HttpAuthenticationMechanism { @Inject private IdentityStoreHandler identityStoreHandler; @Override public AuthenticationStatus validateRequest(HttpServletRequese, HttpServletResponse, HttpMessageContext) throws AuthException { Status = identityStoreHandler.validate(credential); return status; } }
JWT :
A JSON Web Token (JWT) is a JSON object composed of a header, a payload, and a signature with the following format:
header.payload.signature eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJkdWtlIiwiYXV0aCI6IlJPTEVfVVNFUiIsImV4cCI6MTQ5NzU5NzUwMn0.o47IZdvtl5qI91iSH9AcAs9-mirWQtM9ssXH-Edby-dfLOp9HNXLSQV-F4ll20nsC0E6mfjWyECTKSlUXaVLYg
HEADER:
The header component of the JWT contains information about how the JWT signature should be computed. The header is a JSON object in the following format:
{ "typ": "JWT", "alg": "HS512" }
In this JSON, the value of the "typ" key specifies that the object is a JWT, and the value of the "alg" key specifies which hashing algorithm is being used to create the JWT signature component. In our example, we're using the HS512 algorithm, a hashing algorithm that uses a secret key, to compute the signature
PAYLOAD (Claims):
The payload component of the JWT is the data that's stored inside the JWT (this data is also referred to as the "claims" of the JWT). In our example, the server creates a JWT with the user information stored inside of it.
{ "sub": "duke", "auth": "ROLE_USER", "exp": 1497597502 }
In our example, we are putting caller name in "sub" (subject), roles in "auth" (custom defined) and expiration time in "exp" claim into the payload. You can put as many claims as you like. There are several different standard claims for the JWT payload, such as "iss" the issuer, “iat” the issuedAt, “aud” the audience etc. These fields can be useful when creating JWT, but they are optional.
Note : The size of the data will affect the overall size of the JWT that may negatively affect performance and cause latency.
SIGNATURE :
The signature is computed using the following pseudo code:
data = base64urlEncode( header) + “.” + base64urlEncode( payload ); signature = Hash( data, secret );
To get the JWT signature, the data string is hashed with the secret key using the hashing algorithm specified in the JWT header.
Authentication flow :
During authentication, Initially Client logged in by sending their credentials to the server. And the server verifies the credentials using authentication mechanism which invokes an identity store to match the given credentials with a known user detail from the database.
- If a match is found, the Authentication Mechanism retrieves the user data, generates a JWT that will be used to access the services. JWT containing user details(username as the subject) and roles(authorities) so the service provider does not need to go into the database to verify user roles and permissions for each request and it also sets the expiration on the JWT (which is the date and time to show when this JWT will expire). Server signs (and if needed, encrypts) the JWT by the HS512 algorithm where only the server know the secret key and sends it to the client as a response through Authorization header.
- If a match is not found, the Authentication Mechanism reports a failed authentication(401 status code), the caller is not logged in, and is unable to be given authorization.
RememberMe Authentication flow:
In case of rememberme, RememberMeIdentityStore generates the JWT and server sends it to the client as a response through cookies "JREMEMBERMEID".
Authorization flow :
During Authorization, Whenever the user wants to access a protected resource, the user agent(browser) send the JWT in the Authorization header using the bearer schema to the server. For every request, The server takes the JWT from the Authorization header (and decrypts it, if needed), validates the signature using the same signature algorithm HS512. The application verify that the signature obtained from its own hashing operation matches the signature on the
JWT itself.
- If signature is valid which indicates that the API call is coming from an authentic source, extracts the user data and roles. Based on this data solely, and again without looking up further details in the database, it can accept(HTTP code 200) or deny(HTTP code 403) the client request.
- If the signatures is invalid, which may be an indicator of a potential attack on the application, sends an unauthorized(HTTP code 401) response. So by verifying the JWT, the application adds a layer of trust between itself and the user.
Rememberme Authorization flow :
Whenever the user wants to access a protected resource, the user agent would automatically include the JWT in the cookie with JREMEMBERMEID key. It does not require state to be stored on the server because the JWT encapsulates everything the server needs to serve the request.
Wrap-up
This approach allows for great flexibility while still keeping things secure and easy to develop. By using this approach, it is easy to add new server nodes to the service provider cluster, initializing them with only the ability to verify the signature by providing them a shared secret key. No session replication, database synchronization or inter-node communication is required.
Hopefully this article has given some sort of an insight into the power of JWT as well as the new Java EE Security API. You can find the example code of this article in our Payara Examples repository, under Java-EE/security-jwt-example, Just fork it and play with it as you like.
Related Posts
The Payara Monthly Catch - November 2024
Published on 28 Nov 2024
by Chiara Civardi
0 Comments
Moving Beyond GlassFish - Here's Why and How
Published on 11 Nov 2024
by Chiara Civardi
0 Comments
If you’re still managing Java applications on GlassFish middleware, you might be hitting some roadblocks, such as difficulties with automation, lack of integrated monitoring and official support and limited abilities with modern tools. However, ...