Zero Trust Security in Enterprise Java: What it is and How to Implement it
Cybersecurity isn’t just about building walls, fortresses, moats or any other external barrier anymore. Nowadays, it’s important to check […]
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.
{{cta(‘c11b699a-704e-4144-8f8a-6d473cb091c6’)}}
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; } }
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.
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.
In case of rememberme, RememberMeIdentityStore generates the JWT and server sends it to the client as a response through cookies “JREMEMBERMEID“.
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.
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.
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.
{{cta(‘4c7626f5-1e53-418e-90c8-add6e4af19c9′,’justifycenter’)}}
Share:
Cybersecurity isn’t just about building walls, fortresses, moats or any other external barrier anymore. Nowadays, it’s important to check […]
We’re excited to announce that Payara Platform Community 7 Beta application server is now fully certified as Jakarta EE 11 […]
If your Java EE 8 applications run on Red Hat JBoss Enterprise Application Platform (EAP) 7, you can’t afford […]
Your article was exactly what I was looking for since I grabbed your JWT auth example on GitHub 🙂 Thank your for detailing the theory with beautiful sequence diagrams !
Now I released a war of 3 mb because of embeeding some Jackson / sounds like unuseful (?)
io.jsonwebtoken
jjwt
imports also Jackson – doesnt that mean that it duplicates JSON-B ?
Question – do you think turining your sample with an other JWT lib ? or does it plan that jjwt swith to JSON-B ?
Regards
Hi Stephen,
Payara bundles both nimbus-jose-jwt and jackson so either you can use
com.nimbusds:nimbus-jose-jwt lib with provided scope or you may use io.jsonwebtoken:jjwt with excluded Jackson lib using maven .
Thanks for pointing out, In future we will update this sample with nimbus-jose-jwt api.
With HttpSession you set the session timeout and server automatically extends the expiration date if the request is within the range. How can you achieve that with JWT?
Hi Vano,
There is no specific in-built support in EE Security atm to renew the token.
But you may add the renew/refresh token functionality in your app during the token validation by replacing the expiresIn & iat value and coping all other attributes values which is considered to have the same token returned but with a extended expiry time.
Hi. I am new developing backend with Java EE and payara. I tried the example you mentioned and I can authenticate and it effectively gives me the token, but why is the response canceled when it should show me the protected content? Am I missing something? I think it’s the SSL certificate but I still do not know. I would greatly appreciate your help.
Hi Hector,
May be you are trying to access protected resource (e.g ‘/delete’) with less accessibility attributes user (e.g duke). May you share more details about the HTTP error-code, protected endpoint and user name so I can help further.
While calling /login resource as payara , it returns correct username and password as response but subsequently when I try to access /read in SampleController It returns an anonymous user . /write and /delete return unauthorized access. Same is happening when I login with username duke and password secret. What can perhaps go wrong?
I used your code from GitHub for making a sample web application in netbeans. Everything works fine as long as I supply username, password in calling all resources like /read /write /delete. Actually I should have passed them in login only and the credentials must persist there on in SecurityContext. This is not happening. Although delete is protected resource for USER but this too is accessed when I pass admin credentials.
Hi Kamlendu,
‘/login’ endpoint returns the JWT token which further can be used to call other resources.
For example to access ‘/delete’ resource, user should have ADMIN_ROLE group access and pass the JWT in request header.
You may find the details about the groups and users for the sample application, here : https://github.com/payara/Payara-Examples/blob/master/javaee/security-jwt-example/README.md
I understood and went through ReaMe.md file in the project which tells me to put token into authentication header. This is already done createToken Method of AuthenticationMechanism class. When it call /login with username,password and rememberme=true in query string then it must remember the token in succeeding calls to /read. Shall I have a write a filter to put token in REMEMBERID cookie or shall I put the created token in Session. HttpMessageContext does not mentain the state to store token in next call of session.
Maybe I haven’t got the concept well. Pl. help
Hi kamlendu,
Don’t confuse with remember-me flag, JWT does not require state to be stored on the server because It encapsulates all the info server needs to serve the request. It actually defines that JWT token will be presented via a cookie. You may configure the cookie maxAge and other properties using @RememberMe annotation.
After successful authentication, server sends a RememberMe cookie (with JREMEMBERMEID key and JWT token value) which user-agent(browser) automatically sends back to server when requesting another resource (e.g ‘/read’).
I am looking at the example. I notice a complete absence of security realms. So I take it this Java EE Security principle leaves security realms behind?
Let us say I wish to use the same authentication/authorization principle for several wars. I would have to create a jar of the example above and include it in each war?
Java EE Security API provides an alternative way to configure authentication mechanisms and identity stores but it does not mean existing security mechanism ( e.g declaration via web.xml) support are replaced.
Yes, The example.jar can be include in several war (WEB-INF/lib) but make sure to place the beans.xml on the META-INF folder inside the jar root folder .
Please mention in the article, that the JREMEMBERMEID cookie bij default is “Secure”, meaning it won’t be set/sent by your browser, if your browser is accessing Payara over common HTTP instead of HTTPS.
That threw me for a loop.
Hello,
i’m trying to implement HttpAuthenticationMechanism on Payara 5 and i get such error when i try to authenticate the user with my custom implementation using token and I get an error (below the code). I tried with @ApplicationScope and @RequestScope. From time to time it works but in 99% cases it throws UnsatisfiedResolutionException.
Code:
@RequestScoped
public class CustomAuthentication implements HttpAuthenticationMechanism {
@Override
public AuthenticationStatus validateRequest(HttpServletRequest request, HttpServletResponse response, HttpMessageContext context) throws AuthenticationException {
String grantType = request.getParameter(“grant_type”);
try {
switch (grantType) {
case “password”:
return loginUsingPassword(request, response, context);
case “refresh_token”:
return loginUsingToken(request, response, context);
default:
return context.responseUnauthorized();
}
} catch (OauthAuthorizationFailure e) {
return context.responseUnauthorized();
}
}
private AuthenticationStatus loginUsingPassword(HttpServletRequest request, HttpServletResponse response, HttpMessageContext context) throws OauthAuthorizationFailure {
….
}
private AuthenticationStatus loginUsingToken(HttpServletRequest request, HttpServletResponse response, HttpMessageContext context) {
…
}
}
Error from Payara:
[2019-06-10T15:21:48.131+0200] [Payara 5.192] [WARNING] [] [javax.enterprise.system.container.web.com.sun.web.security] [tid: _ThreadID=42 _ThreadName=http-thread-pool::http-listener-1(2)] [timeMillis: 1560172908131] [levelValue: 900] [[
JASPIC: http msg authentication fail
org.jboss.weld.exceptions.UnsatisfiedResolutionException: WELD-001334: Unsatisfied dependencies for type HttpAuthenticationMechanism with qualifiers
at org.jboss.weld.bean.builtin.InstanceImpl.checkBeanResolved(InstanceImpl.java:241)
at org.jboss.weld.bean.builtin.InstanceImpl.get(InstanceImpl.java:113)
at org.glassfish.soteria.mechanisms.jaspic.HttpBridgeServerAuthModule.validateRequest(HttpBridgeServerAuthModule.java:113)
at org.glassfish.soteria.mechanisms.jaspic.DefaultServerAuthContext.validateRequest(DefaultServerAuthContext.java:76)
at com.sun.web.security.realmadapter.JaspicRealm.validateRequest(JaspicRealm.java:391)
at com.sun.web.security.realmadapter.JaspicRealm.validateRequest(JaspicRealm.java:358)
at com.sun.web.security.realmadapter.JaspicRealm.validateRequest(JaspicRealm.java:181)
at com.sun.web.security.RealmAdapter.invokeAuthenticateDelegate(RealmAdapter.java:489)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:470)
at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:724)
at org.apache.catalina.core.StandardPipeline.doChainInvoke(StandardPipeline.java:579)
at com.sun.enterprise.web.WebPipeline.invoke(WebPipeline.java:97)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:159)
at org.apache.catalina.connector.CoyoteAdapter.doService(CoyoteAdapter.java:371)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:238)
at com.sun.enterprise.v3.services.impl.ContainerMapper$HttpHandlerCallable.call(ContainerMapper.java:520)
at com.sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.java:217)
at org.glassfish.grizzly.http.server.HttpHandler.runService(HttpHandler.java:182)
at org.glassfish.grizzly.http.server.HttpHandler.doHandle(HttpHandler.java:156)
at org.glassfish.grizzly.http.server.HttpServerFilter.handleRead(HttpServerFilter.java:218)
at org.glassfish.grizzly.filterchain.ExecutorResolver$9.execute(ExecutorResolver.java:95)
at org.glassfish.grizzly.filterchain.DefaultFilterChain.executeFilter(DefaultFilterChain.java:260)
at org.glassfish.grizzly.filterchain.DefaultFilterChain.executeChainPart(DefaultFilterChain.java:177)
at org.glassfish.grizzly.filterchain.DefaultFilterChain.execute(DefaultFilterChain.java:109)
at org.glassfish.grizzly.filterchain.DefaultFilterChain.process(DefaultFilterChain.java:88)
at org.glassfish.grizzly.ProcessorExecutor.execute(ProcessorExecutor.java:53)
at org.glassfish.grizzly.nio.transport.TCPNIOTransport.fireIOEvent(TCPNIOTransport.java:524)
at org.glassfish.grizzly.strategies.AbstractIOStrategy.fireIOEvent(AbstractIOStrategy.java:89)
at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy.run0(WorkerThreadIOStrategy.java:94)
at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy.access$100(WorkerThreadIOStrategy.java:33)
at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy$WorkerThreadRunnable.run(WorkerThreadIOStrategy.java:114)
at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:569)
at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.run(AbstractThreadPool.java:549)
at java.lang.Thread.run(Thread.java:748)
Hi Krzysztof,
Can you create the Github issue with the reproducer: https://github.com/payara/payara/issues