
Leading the Way: Payara Platform Community 7 Beta Now Fully Jakarta EE 11 Certified
We’re excited to announce that Payara Platform Community 7 Beta application server is now fully certified as Jakarta EE 11 […]
In this blog post I will provide a brief introduction to JASPIC and then take a walk through setting up a basic demo using JASPIC to secure a simple web application in Payara Server.
JASPIC stands for Java Authentication Service Provider Interface for Containers. The original JSR for JASPIC was created back in 2002 but it wasn’t completed until 2007 and wasn’t included in Java EE until Java EE 6 in 2009.
JSR 196 defines a standard service-provider interface (SPI) and standardises how an authentication module is integrated into a Java EE container.
It is supported by all the popular web containers and is mandatory for the full Java EE 6 profile.
It provides a message processing model and details a number of interaction points on the client and server.
A compatible web container will use the SPI at these points to delegate the corresponding message security processing to a server authentication module (SAM).
I will be using the following software whilst doing this walk-through. If you are using different versions then you may see different results.
First of all, we will create a runtime environment so we can run Payara Server from within NetBeans:
In NetBeans go to:
Right-click on your newly created project and select New > Servlet:
This will create a basic servlet. Replace the contents of the processRequest method with the following, adding the required Principal import when prompted:
response.setContentType("text/html;charset=UTF-8");
try (PrintWriter out = response.getWriter())
{
out.println("<!DOCTYPE html>");
out.println("<html>");
out.println("<head>");
out.println("<title>Servlet TestServlet</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>Servlet TestServlet at " + request.getContextPath() + "</h1>");
Principal userPrincipal = request.getUserPrincipal();
boolean adminUser = request.isUserInRole("admin");
String userName;
if (userPrincipal != null)
{
userName = userPrincipal.getName();
}
else
{
userName = "Unknown User";
}
out.println("You are currently authenticated as: " + userName + "<br>");
if (adminUser)
{
out.println("<br>As you're admin you can view this.<br>");
}
else
{
out.println("<br>Sorry, you're not admin. Nothing to see here.<br>");
}
out.println("</body>");
out.println("</html>");
}
This is very basic but will allow us to see the relevant authentication data being returned by the server.
Start your Payara Server – Under Services > Servers, right click on your server and click Start.
To run your servlet, click the Run Project button and the index page should load in your default browser. From there, append TestServlet to the URL and navigate to that page (by default, http://localhost:8080/JASPICTest/TestServlet).
You should get the following response:
You have accessed TestServlet at /JASPICTest2
You are currently authenticated as: Unknown User
Sorry, you’re not admin. Nothing to see here.
The Server Authentication Module (SAM) must implement the javax.security.auth.message.module.ServerAuthModule interface as defined by JSR 196. The Javadoc for the interface can be found here.
The SAM is invoked indirectly by the message processing runtime at the validateRequest and secureResponse interaction points.
Create a new Java project called TestAuthModule, and then create a new Java class in it called TestAuthModule, with a package of fish.payara.
Next:
These will only be used to compile the code, you don’t need to package them up as the web container will already contain copies.
In the TestAuthModule class implement the javax.security.auth.message.module.ServerAuthModule interface, and have NetBeans generate the required interface’s method stubs.In the TestAuthModule class, implement the javax.security.auth.message.module.ServerAuthModule interface, and have NetBeans generate the required interface’s method stubs.In the TestAuthModule class, implement the javax.security.auth.message.module.ServerAuthModule interface, and have NetBeans generate the required interface’s method stubs.In the TestAuthModule class, implement the javax.security.auth.message.module.ServerAuthModule interface, and have NetBeans generate the required interface’s method stubs.In the TestAuthModule class, implement the javax.security.auth.message.module.ServerAuthModule interface, and have NetBeans generate the required interface’s method stubs.In the TestAuthModule class, implement the javax.security.auth.message.module.ServerAuthModule interface, and have NetBeans generate the required interface’s method stubs.In the TestAuthModule class, implement the javax.security.auth.message.module.ServerAuthModule interface, and have NetBeans generate the required interface’s method stubs.In the TestAuthModule class, implement the javax.security.auth.message.module.ServerAuthModule interface, and have NetBeans generate the required interface’s method stubs.In the TestAuthModule class, implement the javax.security.auth.message.module.ServerAuthModule interface, and have NetBeans generate the required interface’s method stubs.In the TestAuthModule class, implement the javax.security.auth.message.module.ServerAuthModule interface, and have NetBeans generate the required interface’s method stubs.
For now we won’t add in anything major besides giving the class something to initialise to, and filling out some methods so that they don’t throw their UnsupportedOperation exceptions:
package fish.payara;
import java.util.Map;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.message.AuthException;
import javax.security.auth.message.AuthStatus;
import javax.security.auth.message.MessageInfo;
import javax.security.auth.message.MessagePolicy;
import javax.security.auth.message.module.ServerAuthModule;
public class TestAuthModule implements ServerAuthModule
{
private CallbackHandler handler;
@Override
public void initialize(MessagePolicy requestPolicy, MessagePolicy responsePolicy, CallbackHandler handler, Map options) throws AuthException
{
System.out.println("initialize called.");
this.handler = handler;
}
@Override
public Class[] getSupportedMessageTypes()
{
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
@Override
public AuthStatus validateRequest(MessageInfo messageInfo,
Subject clientSubject, Subject serviceSubject) throws AuthException
{
System.out.println("validateRequest called");
return AuthStatus.SUCCESS;
}
@Override
public AuthStatus secureResponse(MessageInfo messageInfo, Subject serviceSubject) throws AuthException
{
return AuthStatus.SEND_SUCCESS;
}
@Override
public void cleanSubject(MessageInfo messageInfo, Subject subject) throws AuthException
{
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
}
Build the project, and then copy the jar to <PAYARA_INSTALL_DIR>/glassfish/lib and restart Payara Server.
Next up we need to create a message security provider in Payara Server and then link this to our web app.
Go to the Payara Server Admin console (http://localhost:4848):
Right click the JASPICTest project, and add a new GlassFish Descriptor with default settings, before adding the following to the glassfish-web-app tag in its XML to indicate that the TestSAM you just set up should be used for this app:
<glassfish-web-app httpservlet-security-provider="TestSAM">
Navigate to http://localhost:8080/JASPICTest/TestServlet in your browser, and the servlet should load as before. If you look in the Payara Server console output though, you should see our messages:
INFO: initialize called.
INFO: validateRequest called.
INFO: secureReponse called.
This shows that our SAM is now being used by Payara Server.
OK, so at the moment we have a web app that is linked to our SAM, but we haven’t actually said to secure anything, and even if we did, our SAM simply authenticates anyone!
So, let’s implement some (albeit very basic) security.
NOTE – This is only for demo purposes to show how JASPIC works, it is most definitely not intended to be a way of doing security!
First of all, let’s lock down our servlet. We want to lock it down to only users with the role admin or standard. To do so, we need to create and add the following to an application web.xml. To create the web.xml file, right click the JASPICTest project > New > Other… > Web > Standard Deployment Descriptor. Once you’ve created it (just use the default settings), add the following to it between the web-app tags:
<security-constraint>
<web-resource-collection>
<web-resource-name>JASPICTest</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>standard</role-name>
<role-name>admin</role-name>
</auth-constraint>
</security-constraint>
<security-role>
<role-name>standard</role-name>
</security-role>
<security-role>
<role-name>admin</role-name>
</security-role>
There is one additional (rather ugly) step we need to do to make our app work. In order for GlassFish to accept the roles that our authentication module puts into the JAAS Subject we have to map them to groups.
In order to do so, add the following to the glassfish-web.xml:
<security-role-mapping>
<role-name>standard</role-name>
<group-name>standard</group-name>
</security-role-mapping>
<security-role-mapping>
<role-name>admin</role-name>
<group-name>admin</group-name>
</security-role-mapping>
NetBeans should auto-deploy our changed files to Payara Server once these changes have been saved, but to make sure they’re deployed you can click the “Run Project” button.
Next up we will alter our SAM again to implement the methods. Each of the methods is implemented as follows:
Once we have filled out the above, our TestAuthModule looks like this:
package fish.payara;
import java.util.Map;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.message.AuthException;
import javax.security.auth.message.AuthStatus;
import javax.security.auth.message.MessageInfo;
import javax.security.auth.message.MessagePolicy;
import javax.security.auth.message.callback.CallerPrincipalCallback;
import javax.security.auth.message.callback.GroupPrincipalCallback;
import javax.security.auth.message.module.ServerAuthModule;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class TestAuthModule implements ServerAuthModule
{
@SuppressWarnings("rawtypes")
protected static final Class[] supportedMessageTypes = new Class[] {
HttpServletRequest.class, HttpServletResponse.class };
private CallbackHandler handler;
@Override
public void initialize(MessagePolicy requestPolicy,
MessagePolicy responsePolicy, CallbackHandler handler, Map options)
throws AuthException
{
System.out.println("initialize called.");
this.handler = handler;
}
@SuppressWarnings("rawtypes")
@Override
public Class[] getSupportedMessageTypes()
{
return supportedMessageTypes;
}
@Override
public AuthStatus validateRequest(MessageInfo messageInfo,
Subject clientSubject, Subject serviceSubject) throws AuthException
{
HttpServletRequest request =
(HttpServletRequest)messageInfo.getRequestMessage();
String user = request.getParameter("user");
String group = request.getParameter("group");
System.out.println("validateRequest called.");
System.out.println("User = " + user);
System.out.println("Group = " + group);
authenticateUser(user, group, clientSubject, serviceSubject);
return AuthStatus.SUCCESS;
}
@Override
public AuthStatus secureResponse(MessageInfo messageInfo,
Subject serviceSubject) throws AuthException
{
return AuthStatus.SEND_SUCCESS;
}
@Override
public void cleanSubject(MessageInfo messageInfo, Subject subject)
throws AuthException
{
if (subject != null)
{
subject.getPrincipals().clear();
}
}
private void authenticateUser(String user, String group,
Subject clientSubject, Subject serverSubject)
{
System.out.println("Authenticating user " + user + " in group "
+ group);
CallerPrincipalCallback callerPrincipalCallback =
new CallerPrincipalCallback(clientSubject, user);
GroupPrincipalCallback groupPrincipalCallback =
new GroupPrincipalCallback(clientSubject, new String[]{group});
try
{
handler.handle(new Callback[] { callerPrincipalCallback,
groupPrincipalCallback });
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
Now all we need to do is test our new module.
First of all, build it, and copy the JAR over to <PAYARA_INSTALL_DIR>/glassfish/lib as before, and restart Payara Server.
Now we can test by passing in different dummy credentials.
If you go to http://localhost:8080/JASPICTest/TestServlet?user=Andy&group=standard, you should see the “Sorry, you’re not admin. Nothing to see here.” message.
If you go to http://localhost:8080/JASPICTest/TestServlet?user=Andy&group=admin, you should see the “As you’re admin you can view this.” message.
And if you go to http://localhost:8080/JASPICTest/TestServlet?user=Andy&group=xxx, you should see a HTTP Status 403 – Forbidden message.
Hopefully this has given you a taster of how to use JASPIC to secure your applications, and you can see how relatively straightforward it is to put the basic building blocks in place.
If you’re looking for an example of a SAM that does authentication then there is one available from Oracle here: http://docs.oracle.com/cd/E19798-01/821-1752/gizeb/index.html
Although JASPIC is yet to really take off, it’s a good first step towards standardising security in web containers, and avoids the need for each to have their own proprietary solution. That being said, there is still the issue of different containers using different deployment descriptors, hindering the portability of apps.
Share:
We’re excited to announce that Payara Platform Community 7 Beta application server is now fully certified as Jakarta EE 11 […]
Enterprise Java applications power global commerce, healthcare, government and countless other industries. These systems must be scalable, secure and […]
May 2025 marks a monumental milestone in software development: Java turns 30. The impact of this language on the […]
At JavaOne I finally got some time to mess around with JASPIC. So far I’m hugely impressed. It’s really a powerful solution for getting the prinicpal and roles passed to the rest of the application.
I’m having a problem implementing this the security though. I am using Payara 4.1.1.161 with JDK 1.8.0_65. As a test, I hard-coded my validateRequest() method with hard coded user=”adminjaspic” and group=”admin”
My inital test was perfectly successful. This is what I had:
web.xml
———-
JASPICTest
/webresources/admin
itcrowd
glassfish-web.xml // notice mapping from itcrowd to admin
———————-
itcrowd
admin
AdminResource.java
—————————
@Path(“admin”)
@DeclareRoles({“itcrowd”})
public class AdminResource {
@Context
private UriInfo context;
private ThothTimeFormat timeFormat = new ThothTimeFormat();
public AdminResource() {}
@GET
@RolesAllowed({“itcrowd”})
@Produces(MediaType.TEXT_HTML)
public String getText(@Context SecurityContext context) {
Principal p = context.getUserPrincipal();
String retval = “admin”;
retval += String.format(“t=[%s]”, timeFormat.get());
retval += String.format(“p=[%s]”, p);
return retval;
}
}
With these options, everything seemed to work OK. The Auth module was hard coded to adminjaspic/admin to simulate the user/role from some authentication system. The web.xml used an application-specific “itcrowd” to protect a JAX-RS endpoint. The glassfish-web.xml mapped the application-specific “itcrowd” to the authentication system “admin” role. Finally, the JAX-RS endpoint had a @DeclarRoles(“itcrowd”) and the @GET method should only be executed by @RolesAllowed(“itcrowd”). With this configuration, I can successully call the JAX-RS service and see the results in the browser.
As a next step, I altered glassfish.web.xml so that the security role mapping mapped to a group which isn’t coming from the Auth module. Recall my Auth module is hard-coded to return “admin” as the group. I updated glassfish-web.xml to use “does_not_exist” as the group instead.
itcrowd
does_not_exist
This was the only change. Everything else remains the same. When I do this test, I get a good result. Because the Auth module is returning “admin” as the group and glassfish-web.xml is mapping to the “does_not_exist” group, when I attempt to navigate to the JAX-RS service with a browser I get a 403 Forbidden error. That’s good, it means security is working.
Now here is the next test which I think isn’t so good. I went back to glassfish-web.xml and changed the mapping back to “admin”. So it’s back to it’s orginal state;
itcrowd
admin
But now I change the roles in the JAX-RS class annotations to @DeclareRoles({“does_not_exist”}) and @RolesAllowed({“does_not_exist”}). After making these changes, I expected the request to the JAX-RS endpoint to fail with a 403 error since since the “does_not_exit” role doesn’t exists anywhere in my configuration. But that’s not the behavior I see. Instead of getting a 403 error, I can successfully access the JAX-RS endpoint.
So, long story short, I expected the request to the JAX-RS endpoint to fail with a 403 error since since the “does_not_exit” role doesn’t exists anywhere in my configuration, but it didn’t. So…
1) Is my expectation wrong? If so, why?
2) Is there something wrong in my JASPIC configuration?
3) Is something not working the way it’s supposed to?
Thanks
Hi Michael,
Can you raise this on GitHub please? If you include a link to a repository with the code snippets above on, it would be very helpful too!
https://github.com/payara/Payara/issues
Thanks,
Mike
Nice article. What is the asadmin command to install the provider? I’m looking to do this with Micro 174.
Simple, intuitive and very concise. The article goes straight to the point. Thanks to other code contributors in comment section.