Using JASPIC to Secure a Web Application in Payara Server

Photo of Andy Overton by Andy Overton

 

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.

 

What is JASPIC?

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).

 

Walkthrough

I will be using the following software whilst doing this walk-through. If you are using different versions then you may see different results.

  • OpenSUSE Leap 42.1
  • NetBeans 8.1
  • JDK 1.8.0_72
  • Payara Server 4.1.1.161.1

Creating the Runtime Environment

First of all, we will create a runtime environment so we can run Payara Server from within NetBeans:

  • Tools > Servers
  • Click Add Server…
  • Select GlassFish Server
  • Set the installation directory to where you’ve unzipped Payara Server.
  • All other defaults should be OK.

Creating the web application

  1. So, next we will create a very basic web-app consisting of a single servlet.

In NetBeans go to:

  • File > New Project > Java Web > Web Application
  • Name - JASPICTest
  • Make sure the target server is your Payara Server install
  • Click Finish

Right-click on your newly created project and select New > Servlet:

  • Class Name - TestServlet
  • Package – fish.payara
  • Click Next and Finish.

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.

 

Testing your application

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.

 

Creating the Server Authentication Module

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:

  • Right click the project and select Properties > Libraries.
  • Click Add JAR/Folder
  • Add the following dependencies (they can both be found in <PAYARA_INSTALL_DIR>/glassfish/modules):
    • servlet-api.jar
    • security.auth.message-api.jar
  • Click OK

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.

 

Configuring the SAM

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):

  • Go to Configurations > server-config > Security > Message Security > HttpServlet
  • Select the Providers
  • Click New:
    • Provider ID - TestSAM
    • Provider Type - server
    • Class Name - fish.payara.TestAuthModule
  • Click OK

Updating the web app

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">

 

Testing the changes

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.

 

Locking down resources

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:

  • initialize - Simply takes the CallBackHandler and instantiates our local handler.
  • getSupportedMessageTypes - Returns the HTTP servlet request and response types.
  • secureResponse - Simply returns Success.
  • cleanSubject - Clears all principals from the Subject.
  • validateRequest - This is the main method of interest. In order to pass in the user and role, I have added them as servlet request parameters for testing purposes. This method extracts those values and then calls authenticateUser.
  • authenticateUser - NOTE - This method doesn't actually do any authentication! It simply takes the user and group, creates callback classes from them, and passes them to the callback handler.

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();
        }
    }
}

 

 

Testing

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.

 

Wrapping Up

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.

 

 

 

 

Comments