Wednesday, May 22, 2013

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...


14 comments:

Anonymous said...

Is there anyone you can post your sample code. I must be missing something because my annotations do not work.

Thanks,

Anonymous said...

I must be missing something too. The autowired field is not getting initialized by Spring

Heero said...

I thought spring-security was stateful and managed via sessions on the server side. What happens when you login and hit other pages? Are you sending the credentials and authorizing every time? I don't see where this is happening, especially since you're using the standard spring filter.

Anonymous said...

Nice blog. I have found following blog useful as well
http://www.srccodes.com/p/article/35/spring-security-custom-login-form-example

Anonymous said...

Any sample code?

Tiklup said...

Thanks for the tutorial! I'm running into this exception when calling the @PreAuthorize method with the correct credentials:

org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext

Did you run into something similar and fix it?

Anonymous said...

I am not able to make the pre-post annotations work on the methods. How did you not get into this issue -
http://stackoverflow.com/questions/23016333/preauthorize-annotation-doesnt-with-jersey

Anonymous said...

Thank you, we were missing the cglib dependency and were stuck until we spotted your post.

Thanks again.

Anonymous said...

Hi,

but if i want to check the security via Browser i can?

Alexandre de Pellegrin said...

Yes, if you access the secured REST service from a web browser, you will be prompted to enter the login/password credentials.

Anonymous said...

ok but via java code how i can pass user1 and strongpassword1? in the header i put property name=user1 and password=strongpassword1? or where?

thanks!!

Anonymous said...

i try with set:

request().header("Authorization: ", "Basic " + "dXNlcjE6c3Ryb25ncGFzc3dvcmQx")

but the response is Unauthorized

simashree said...

Thanks for the great information in your blog Selenium Training in Chennai

Anonymous said...

How can i do certificate based authentication ?
I am using filters (JAVXRS) to before accessing the web app .
How can be this done ,Get client cert and do validation in filter ?