Skip to main content

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


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


Popular posts from this blog

DCcduino usb drivers (CH340 / CH341 chipset)

I've just received my first arduino platform. It's a DCcduino board (a clone of Arduino Uno). As I had some difficulties to have it recognised by my MacBook, I decided to share its drivers. This card has a CH340 USB-to-serial chip. You can find drivers for this chip on the web site of the chinese manufacturer, here :

http://www.wch.cn/downloads.php?name=pro&proid=5

Or download it directly from my Google Drive. The archive contains drivers for Mac, Linux and Windows platforms.

https://drive.google.com/file/d/0B5okZr5AW4gaX2pZaWt6dVNaSFU/edit?usp=sharing

I hope this will help somebody.

UPDATE for Mac users with Yosemite :

Please, follow this extra instructions :
Install the CH340 driverRun the command in Terminal: sudo nvram boot-args="kext-dev-mode=1"Reboot

The great alternative to JRebel

I'm an old user and addict of JRebel. I started to use it on open source projects and in professional contexts. From the beginning, I've been convinced that the licensing mode was wrong because of its lifetime. Asking for license renewal each year is boring. So, I decided to look for FREE alternative solutions and finally I recently found one.

This solution is efficient for maven projects developed with Eclipse. It is base on :

Hotswap Agent project : http://hotswapagent.org/Dynamic Source Lookup plugin for Eclipse : https://github.com/ifedorenko/com.ifedorenko.m2e.sourcelookup
I tested it with Java 7 & Java 8. I work on web applications that run on Tomcat. I developed wih Spring (IoC), sometimes Hibernate and Vaadin



Hotswap Agent installation consists on the deployment of a patch for your JVM. You just have to download the corresponding patch here : https://github.com/dcevm/dcevm/releases Then, download the hotswap-agent.jar from here : https://github.com/HotswapProjects…