How to secure REST services exposed with Jersey (JAX-RS) using Spring Security

This is the challenge I had to perform. I have to open services to business partners and I want to secure them. I use a Java based application with Jersey API (JAX-RS).

The first thing to consider is : what kind of solution offers the best compromise between security and the effort I'll have to provide to maintain this solution?
After some discussions with developers and experts, the conclusion is : expose your services over https and use Basic authentication (Digest authentication and certificate based authentication are too complex for partners)

So, how to implement that? I delegate "https" to my Apache http servers. But I still need to handle authentication (and authorization of course). After long hours on Google, I understood that it is possible to manage security with Jersey by many many ways. Here is a short list :

  1. Delegate simple authentication and authorization to your container (Tomcat) or to your frontend (Apache)
  2. Delegate authentication to your container or frontend and let Jersey manage authorization with JSR-250 annotations such as @PermitAll, @DenyAll, @RolesAllowed written on methods. (For example : http://www.butonic.de/2010/06/18/a-simple-jax-rs-security-context-example-in-glassfish/)
  3. Be agnostic from any container  and let your "Jersey-based" application take in charge authentication and authorization by implementing your own ContainerRequestFilter. You can read these two excellents posts : http://simplapi.wordpress.com/2013/01/24/jersey-jax-rs-implements-a-http-basic-auth-decoder/ and http://anismiles.wordpress.com/2012/03/02/securing-versioning-and-auditing-rest-jax-rs-jersey-apis
  4. Be agnostic from any container like the solution 3 but let Spring Security manage authentication and authorization.
Of course, I choose the solution 4. With this solution, I didn't have to filter any URL (like I commonly do when I configure Spring Security with "url-interceptor"). I  let all URLs opened to anonymous user BUT I told Spring Security to filter method calls. As Jersey maps one service to one method, I  obtained what I needed : security over Jersey services.

Here are the stepsto implements that :
  1. Update your maven configuration (optional)
  2. Add a servlet filter to enable Spring Security (web.xml)
  3. Update my Spring configuration (dedicated applicationContext-security.xml file)
  4. Add annotation on methods I want to secure (Java code)
Dependencies to add to the pom.xml

 <!-- SECURITY (start) -->
 <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>2.2.2</version>
        </dependency>
 <!-- SECURITY (end) -->



In the web.xml :

    <!-- Filter to secure Jersey (JAX-RS) services -->
    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>
            org.springframework.web.filter.DelegatingFilterProxy
        </filter-class>
    </filter>
    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping> 

The applicationContext-security.xml file :

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:security="http://www.springframework.org/schema/security"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">

 <!-- To allow public access by default and to set authentication mode to basic login/password -->
 <security:http>
  <security:anonymous enabled="true" />
  <security:http-basic />
 </security:http>

 <!-- To delegate authorization to method calls rather than to urls -->
 <!-- (Thus, we don't need to set any url-interceptor in this conf) -->
 <security:global-method-security pre-post-annotations="enabled" />
 
 <!-- To create user/password with roles -->
 <security:authentication-manager alias="authenticationManager">
  <security:authentication-provider>
   <security:user-service>
    <security:user authorities="ROLE_DUMMY
name="user1" password="strongpassword1" />
   </security:user-service>
  </security:authentication-provider>
 </security:authentication-manager>
</beans>

A Java method exposed as REST service with Jersey

@Component
@Path("/userinfo")
public class UserInfoService {
 
@Autowired
private UserInfoDao dao;
 
@GET @Path("{userid}")
@Produces({MediaType.TEXT_XML})
@PreAuthorize("hasRole('ROLE_DUMMY')")
public UserInfo findUserInfoByCode(@PathParam("userid") String userid) {
  return dao.getUserInfo(userid);
}
...



If I want to expose a service to anonymous users, I just have to change @PreAuthorize("hasRole('ROLE_DUMMY')") to @PreAuthorize("permitAll") or simply remove the annotation.


That's all! Of course, Spring saved my day once again...