ObjectHunter
Hunting and gathering in the JEE landscape

I am Frank, a freelance Java developer specialized in backend development from south western germany.

Securing a RESTful JPA WebApp with Tomcat's JDBCRealm and Jersey via HTTP Basic-Auth

posted by fas on 2011-04-02 . Tagged as programming, jersey, jsr250, java, tomcat, hibernate, jee

I want to describe a way to secure a JAX-RS webapp using HTTP Basic-Auth in Apache Tomcat with a JDBCRealm.
There's always the question of how to secure a RESTful webapp without breaking one of the big REST paradigms: Do not maintain session state on the server side! But because I was so used to handling user contexts via sessions, I was forced to rethink. One way to go is HTTP-Basic Auth a s described in RFC2617. But one has to go through some steps in order to marry a domain model of users and groups to Tomcat's authentication service, so that the comfort of managing users through a JPA framework like Hibernate is not lost.

UPDATE: This demo uses unsalted SHA-256 hashes, since the default Tomcat JDBCRealm does not support adding salt to hashes, but there's a great article describing a SaltAwareJDBCRealm.

First of all some changes in Tomcat's server.xml are necessary, so that a JDBC connection will be used to lookup users in a database. This is easily done by using a JDBCRealm instead of the default UserDatabaseRealm. By the way, one does not have to worry a great deal about the performance impact of the database calls needed for authentication, since Tomcat's JDBCRealm has a nice caching feature. Please see the documentation for more information.
But we have to provide JDBCRealm with the user data in a special format. As described in the book 'Professional Apache Tomcat 6' the user and group table accessed by JDBCRealm have to be in the following format:

+----------------------+    +-------------------+
|     tomcat_users     |    |   tomcat_groups   |
+----------------------+    +-------------------+
| username     varchar |    | username  varchar |
| passwordhash varchar |    | groupname varchar |
+----------------------+    +-------------------+

Although one can choose the table and column names freely I had to stick to this database scheme so Tomcat would authenticate users via the JDBCRealm.
This put my in a tight spot, since I already had a Domain Model for Users and Groups defined via JPA Entities and the resulting database scheme looked nothing like JDBCRealm's expectations.

The following simple JPA Entities with a simple m:n association from UserDetails to Group.

@Entity
@Table(name = "cr_users")
public class UserDetails{
    @Id
    @GeneratedValue
    private long id;
    private String name;
    private String passwordHash;
    @ManyToMany
    private Set<Group> groups;
}

@Entity
@Table(name = "cr_groups")
public class Group {
    @Id
    @GeneratedValue
    private long id;
    private String name;
}

yields a database scheme something like this:
+----------------------+  +-------------------+  +--------------------+
|     cr_users         |  |      cr_groups    |  | cr_users_cr_groups |
+----------------------+  +-------------------+  +--------------------+
| id              long |  | id           long |  | cr_users_id   long |
| name         varchar |  | name      varchar |  | groups_id     long |
| passwordhash varchar |  +-------------------+  +--------------------+
+----------------------+


So for JDBCRealm to be able to read those entries from the database a View can be created which aggregates the necessary user data in the expected format. In this simple example the following 2 SQL queries can be used to create the View in the database.

create view tc_realm_groups as
select 
  cr_users.name as username,
  groups.name as groupname
from cr_users 
left join (
	select 
		cr_users_cr_groups.cr_users_id,cr_groups.name 
	from cr_groups 
	left join 
		cr_users_cr_groups 
		on cr_users_cr_groups.groups_id=cr_groups.id
) as groups on groups.cr_users_id=id;

create view tc_realm_users as
select 
  name as username
from cr_users;


Those two new views in the databse meet the JDBCRealm's required format:

+----------------------+  +-------------------+
|   tc_realm_users     |  | tc_realm_groups   |
+----------------------+  +-------------------+
| username     varchar |  | username  varchar |
| passwordhash varchar |  | groupname varchar |
+----------------------+  +-------------------+


The requirements for enabling the JDBCRealm are matched by the new view and the server.xml's Realm element can be changed:

<Realm className="org.apache.catalina.realm.JDBCRealm"
       driverName="org.postgresql.Driver"
       connectionURL="jdbc:postgresql://localhost:5432/mydb"
       connectionName="myuser" connectionPassword="mypass"
       userTable="tc_realm_users" userNameCol="username" userCredCol="passwordhash" 
       userRoleTable="tc_realm_groups" roleNameCol="groupname"
       digest="sha-256"/>


where userNameCol is the column name under which the username can be found in both tables. After adding some test users being member of a “manager-gui” Group it is possible to test the JDBCRealm by trying to log into Tomcat's management console, which does require an authenticated user that is a member of the group “manager-gui”. When using digest=”sha-256”, JDBCRealm uses a SHA-256 algorithm to create a password hash to check against. This means that the users in the database also have to have a SHA-256 checksum and not a plaintext password, which is generally a good idea.

If JDBCRealm authentication works one can add the security constraints to the web.xml, so Tomcat challenges any anonymous requests and relays the user info into Jersey's SecurityContext, from which the Controller's will aqcuire user data in the application. Also we are redirecting any user from a unsecured to a SSL-encrytped Socket, since BASIC-Auth passwords are transmitted unencrypted with every request.The <auth-constraint> element tells Tomcat which group a user must belong to in order to access the webapp.

<security-constraint>
	<web-resource-collection>
		<web-resource-name>my webapp</web-resource-name>
		<url-pattern>/*</url-pattern>
	</web-resource-collection>
	<user-data-constraint>
		<transport-guarantee>CONFIDENTIAL</transport-guarantee>
	</user-data-constraint>
	<auth-constraint>
		<role-name>webapp-user</role-name>
	</auth-constraint>
</security-constraint>
<login-config>
	<auth-method>BASIC</auth-method>
	<realm-name>My secured Webapp</realm-name>
</login-config>

And finally we will need to tell Tomcat to initalize the Jersey Servlet with the the proper parameters to ensure the “@RolesAllowed” constraints from JSR-250 are considered by the web application:

<servlet>
	<servlet-name>Jersey Spring Web Application</servlet-name>
	<servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
	<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>


Tada! Now it's possible to use the javax.security annotations (@RolesAllowed) and have jersey provide the username via a SecurityContext:

@Path("/test")
@Component
public class MyResource {
	private static final Logger log = LoggerFactory
			.getLogger(MyResource.class);

	@GET
	@Path("/{id}")
	@Produces(MediaType.TEXT_HTML)
	@RolesAllowed({"webapp-user"})
	public Response getEvent(@Context SecurityContext sc,@PathParam("id") long id) {
		log.debug("auth: " + sc.getAuthenticationScheme());
		log.debug("user: " + sc.getUserPrincipal().getName()); // the username!
		log.debug("admin-privileges: " + sc.isUserInRole("webapp-admin"));
		return Response.ok(“auth success”).build();
	}
}


The drawbacks of this approach are the tight coupeling to Tomcat as an application server, although configuring a Database Realm should be possible in every Application Server, and the authentication mechanism for the whole Application Server has to be changed, not just a single webapp.


I used the following application stack for this example:

  • tomcat 7.0.11
  • spring 2.5.6
  • jersey 1.6
  • hibernate 3.3
  • hibernate-annotations 3.4
  • jsr250-api 1.0
  • postgresql 9.0.3


Thanks go out to to Eric Warriner who's blogentry about JSR-250 and Tomcat has been of great value!

Links,Sources and further reading:


Tags: programming, jersey, jsr250, java, tomcat, hibernate, jee