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:
- Use
SecurityContext
in your Java code to access the authentication information. - To enable HTTP Basic Auth add a
<security-constraint>
section to yourweb.xml
- Map user roles to GlassFish groups by creating a
sun-web.xml
- 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.
Really nice description to setup the Realm. Thanks for that.
Daniel
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 ?
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?
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?‘
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
Nice article. Specific and to the point. Thanks a lot.