A simple JAX-RS security context example in GlassFish

When creating a REST api with Java EE 6 and JAX-RS there comes the time when you start thinking about security. In our case we were trying to set up HTTP Basic Auth for the REST api to identify users and keep them from deleting other peoples stuff. It took me a while to understand the different aspects of configuring HTTP Basic Auth when using GlassFish:

  1. Use SecurityContext in your Java code to access the authentication information.
  2. To enable HTTP Basic Auth add a <security-constraint> section to your web.xml
  3. Map user roles to GlassFish groups by creating a sun-web.xml
  4. Configure a FileRealm / JDBCRealm in GlassFish to store user passwords

I will detail the steps with a simple deleteRating() example and xml snippets.

The REST bean

First, let me show you the current deleteRating() implementation:

import javax.ws.rs.DELETE;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;

@Path("/api/")
public class RestBean {

    @DELETE
    @Path("ratings/{id}")
    public Response deleteRating( @PathParam("id") int id ) {
        // code here: delete the rating, no matter what
    }

}

Right, no security whatsoever. We need to change that!

Security annotations in the Java Code

To access the authentication information we have to add the @Context:

import javax.ws.rs.core.Context;
import javax.ws.rs.core.SecurityContext;
//...

@DELETE
@Path("ratings/{id}")
public Response deleteRating( @PathParam("id") int id, @Context SecurityContext context ) {
    String username = context.getUserPrincipal().getName();
    // code here: check if he is the owner of the rating
    // code here: deny the request or
    // code here: delete the rating
}

Using the SecurityContext we can get the authenticated username and start the authorization. So much for the easy part. On to xml configuration hell!

Security constrains in the web.xml

The SecurityContext remains useless until we add a <security-constraint> section to the web.xml. Eric Warriner has written up a tomcat example which leads to something like this:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  <display-name>your service name</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
  </welcome-file-list>
  <servlet>
    <servlet-name>Jersey REST Service</servlet-name>
    <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
    <init-param>
      <param-name>com.sun.jersey.config.property.packages</param-name>
      <param-value>de.uks.nt2od.service</param-value>
    </init-param>
    <!-- to use JSR 250 Role Based Authentication and annotations
         like @RolesAllowed uncomment the next init-param section -->
    <!--init-param>
      <param-name>com.sun.jersey.spi.container.ResourceFilters</param-name>
      <param-value>com.sun.jersey.api.container.filter.RolesAllowedResourceFilterFactory</param-value>
    </init-param-->
  </servlet>
  <servlet-mapping>
    <servlet-name>Jersey REST Service</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>

  <!-- which resources should be protected -->
  <security-constraint>
    <web-resource-collection>
      <web-resource-name>Secure</web-resource-name>
      <!-- be specific about the urls and methods here -->
      <url-pattern>/api/ratings/*</url-pattern>
      <http-method>DELETE</http-method>
    </web-resource-collection>
    <auth-constraint>
      <description>has to be a USER</description>
      <role-name>USERS</role-name>
    </auth-constraint>
  </security-constraint>

  <!-- which realm to use for basic auth -->
  <login-config>
    <auth-method>BASIC</auth-method>
    <realm-name>your_realm_name</realm-name>
  </login-config>
  <security-role>
    <role-name>USERS</role-name>
  </security-role>
</web-app>

This would work in tomcat if a USERS role was defined in the tomcat-users.xml. For GlassFish we are not done yet…

Role mapping in the sun-web.xml

GlassFish uses principals and groups of a realm that have to be mapped to role names for the application.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sun-web-app PUBLIC '-//Sun Microsystems, Inc.//DTD
Application Server 9.0 Servlet 2.5//EN'
'http://www.sun.com/software/appserver/dtds/sun-web-app_2_5-0.dtd'>
<sun-web-app>
  <security-role-mapping>
    <role-name>USERS</role-name>
    <group-name>USERS</group-name>
  </security-role-mapping>
</sun-web-app>

We are nearly done. Only one step left!

Setting up a GlassFish FileRealm

Open the admin console and navigate to Configuration -> Security -> Realms. Create a new security realm:

Name your_realm_name (use the one from your web.xml)
Class Name com.sun.enterprise.security.auth.realm.file.FileRealm
JAAS Context fileRealm (yep, a magic string)
Key File /path/to/users_and_passwords.xml

You can then add users to the realm which can be authenticated with HTTP Basic Auth as defined in the web.xml.

For a dynamic web application you may prefer to use the JDBCRealm, which is what we are going to set up next for nt2od.

, , , , , ,

About Jörn Dreyer

learned in a bank, studied business informatics, took the red pill and went down the rabbit hole of software engineering, working on my Ph.D.
  • http://block.happy-coding.com Daniel

    Really nice description to setup the Realm. Thanks for that.

    Daniel

  • Jani

    This is a really good example, but i also need a client for that in Java and i don`t know how to pass username and password in client, can you help me with that, do you have a working example ?

  • sreeram duvur

    Nice and simple example Jorn. A question regarding further authorization checks:

    public Response deleteRating( @PathParam(“id”) int id, @Context SecurityContext context ) {
    String username = context.getUserPrincipal().getName();
    // code here: check if he is the owner of the rating
    // code here: deny the request or
    // code here: delete the rating
    }What would be really nice is to lift the check for whether it is the OWNER of the rating is indeed the one performing DELETE or the ADMINISTRATOR, outside the method. I see that as a part of authorization that is not solved entirely by @RolesAllowed:disqus  annotation. I was thinking something along lines of another Annotation that could take the SecurityContext and perform some user defined check and return 403, if necessary. That would keep the business logic separate from all Authorization checks. Any suggestions on how to accomplish that?

  • sskumar86

    Hi,
    I am using apache CXF for JAX-RS and hosting in tomcat as a war file.
    How to specify ‘user role so that I can authenticate the user in my business logic in server side?I am not using glass fish.I don’t have ‘sun-web.xml’….what shall i do?..can u give me a simple example for this?’

  • Nick

    Hi,

    Is there any way to get the user ID instead of USERNAME from the context? In my DB I have User table with USERNAME and ID fields. All other tables are linked with ID thus it would be great if I can get the user ID from the context. I use jaasRealm in Glassfish with j_security_check.

    Currently I’m authenticating with USERNAME and PASSWORD, when I call request.getUserPrincipal().getName() I get USERNAME.

    Thanks