Finally made it work!
Here are the steps to make it work in Tomcat and Jersey.
Let's assume we have the following contents in TOMCAT_HOME/conf/tomcat-users.xml, where we define 2 roles - editor and member.
We also define three users - gavin, julie, and admin.
<?xml version="1.0" encoding="UTF-8"?>
<role rolename="editor"/>
<role rolename="member"/>
<user username="admin" password="qwerty" roles="editor,member"/>
<user username="gavin" password="qwerty" roles="editor"/>
<user username="julie" password="qwerty" roles="member"/>
</tomcat-users>
Step 1. Make sure you use Servlet 3.0 spec in web.xml
Security annotations do not work with Servlet 2.5 and below.
<?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"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
</web-app>
Step 2. Create your Application Class to enable Security Annotations
NOTE: The code below is specific to Jersey.
package ph.activelearning.rest.security;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature;
public class MyApplication extends ResourceConfig {
public MyApplication() {
super(TestResource.class);
register(RolesAllowedDynamicFeature.class);
}
}
Step 3. Specify your Application Class in web.xml
Make sure that the Application class you created in Step 2 is recognized by Jersey.
<servlet>
<servlet-name>Jersey Web Application</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>ph.activelearning.rest.security</param-value>
</init-param>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>ph.activelearning.rest.security.MyApplication</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
Step 4. Create security-constraint in web.xml
Ironically, even though we want to use security annotations, we still need to define a security constraint in web.xml.
In the example below, we try to secure access to /test/*. It's important that you don't specify any HTTP methods. (ex. <http-method>GET</http-method>
) This means that you're denying access to all HTTP methods.
Still, you need to define all roles that can have access to the URL regardless of the method, through the <auth-constraint>
element.
<web-app …>
<security-constraint>
<web-resource-collection>
<web-resource-name>test</web-resource-name>
<url-pattern>/test/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>editor</role-name>
<role-name>member</role-name>
</auth-constraint>
</security-constraint>
</web-app>
Step 5. Specify authentication method in web.xml
The example below illustrates BASIC authentication.
<web-app …>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>test</realm-name>
</login-config>
</web-app>
Step 6. Define Security Roles
The security roles should correspond to the same roles defined in tomcat-users.xml. In this example, we define the roles editor and member.
NOTE: It seems that this step is optional since authentication / authorization still works even without it.
<web-app ...>
<security-role>
<description>This is editor</description>
<role-name>editor</role-name>
</security-role>
<security-role>
<description>This is member</description>
<role-name>member</role-name>
</security-role>
</web-app>
Step 7. Annotate your resources
package ph.activelearning.rest.security;
import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.SecurityContext;
@Path("test")
@PermitAll
public class TestResource {
@GET
@Path("editor")
@Produces(MediaType.TEXT_PLAIN)
@RolesAllowed("editor")
public String editorOnly() {
return "Got to editor path!";
}
@GET
@Path("member")
@Produces(MediaType.TEXT_PLAIN)
@RolesAllowed("member")
public String memberOnly() {
return "Got to member path!";
}
@GET
@Path("open")
@Produces(MediaType.TEXT_PLAIN)
public String open(@Context SecurityContext context) {
return "Open to all! - " + context.getUserPrincipal().getName();
}
}
That's it. Here's the complete web.xml for your programming pleasure.
<?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"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<servlet>
<servlet-name>Jersey Web Application</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>ph.activelearning.rest.security</param-value>
</init-param>
<!-- Define the Application class where we enable security annotations -->
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>ph.activelearning.rest.security.MyApplication</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Jersey Web Application</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<!-- Required even though we use annotations -->
<security-constraint>
<web-resource-collection>
<web-resource-name>test</web-resource-name>
<url-pattern>/test/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>editor</role-name>
<role-name>member</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>test</realm-name>
</login-config>
<!-- Optional: Define security roles that are defined in your app server -->
<!--
<security-role>
<description>This is editor</description>
<role-name>editor</role-name>
</security-role>
<security-role>
<description>This is member</description>
<role-name>member</role-name>
</security-role>
-->
</web-app>