Enterprise Java

Securing a RESTful Web Service with Spring Security 3.1, part 3

1. Overview

This tutorial shows how to Secure a REST Service using Spring and Spring Security 3.1 with Java based configuration. The article will focus on how to set up the Security Configuration specifically for the REST API using a Login and Cookie approach.

2. Spring Security in the web.xml

The architecture of Spring Security is based entirely on Servlet Filters and, as such, comes before Spring MVC in regards to the processing of HTTP requests. Keeping this in mind, to begin with, a filter needs to be declared in the web.xml of the application:

<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 filter must necessarily be named ‘springSecurityFilterChain’  to match the default bean created by Spring Security in the container.

Note that the defined filter is not the actual class implementing the security logic but a DelegatingFilterProxy with the purpose of delegating the Filter’s methods to an internal bean. This is done so that the target bean can still benefit from the Spring context lifecycle and flexibility.

The URL pattern used to configure the Filter is /* even though the entire web service is mapped to /api/* so that the security configuration has the option to secure other possible mappings as well, if required.

3. The Security Configuration

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

   <http entry-point-ref="restAuthenticationEntryPoint">
      <intercept-url pattern="/api/admin/**" access="ROLE_ADMIN"/>

      <form-login authentication-success-handler-ref="mySuccessHandler" />

      <logout />
   </http>

   <beans:bean id="mySuccessHandler"
    class="org.rest.security.MySavedRequestAwareAuthenticationSuccessHandler"/>

   <authentication-manager alias="authenticationManager">
      <authentication-provider>
         <user-service>
            <user name="temporary" password="temporary" authorities="ROLE_ADMIN"/>
            <user name="user" password="user" authorities="ROLE_USER"/>
         </user-service>
      </authentication-provider>
   </authentication-manager>

</beans:beans>

Most of the configuration is done using the security namespace – for this to be enabled, the schema locations must be defined and pointed to the correct 3.1 XSD versions. The namespace is designed so that it expresses the common uses of Spring Security while still providing hooks raw beans to accommodate more advanced scenarios.

3.1. The <http> element

The <http> element is the main container element for HTTP security configuration. In the current implementation, it only secured a single mapping: /api/admin/**. Note that the mapping is relative to the root context of the web application, not to the rest Servlet; this is because the entire security configuration lives in the root Spring context and not in the child context of the Servlet.

3.2. The Entry Point

In a standard web application, the authentication process may be automatically triggered when the client tries to access a secured resource without being authenticated – this is usually done by redirecting to a login page so that the user can enter credentials. However, for a REST Web Service this behavior doesn’t make much sense – Authentication should only be done by a request to the correct URI and all other requests should simply fail with a 401 UNAUTHORIZED status code if the user is not authenticated.

Spring Security handles this automatic triggering of the authentication process with the concept of an Entry Point – this is a required part of the configuration, and can be injected via the entry-point-ref attribute of the <http> element. Keeping in mind that this functionality doesn’t make sense in the context of the REST Service, the new custom entry point is defined to simply return 401 whenever it is triggered:

@Component( "restAuthenticationEntryPoint" )
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint{

   @Override
   public void commence( HttpServletRequest request, HttpServletResponse response, 
    AuthenticationException authException ) throws IOException{
      response.sendError( HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized" );
   }
}

3.3. The Login Form for REST

There are multiple ways to do Authentication for a REST API – one of the default Spring Security provides is Form Login – which uses an authentication processing filter – org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.

The <form-login> element will create this filter and will also allow us to set our custom authentication success handler on it. This can also be done manually by using the <custom-filter> element to register a filter at the position FORM_LOGIN_FILTER – but the namespace support is flexible enough.

Note that for a standard web application, the auto-config attribute of the <http> element is shorthand syntax for some useful security configuration. While this may be appropriate for some very simple configurations, it doesn’t fit and should not be used for a REST API.

3.4. Authentication should return 200 instead of 301

By default, form login will answer a successful authentication request with a 301 MOVED PERMANENTLY status code; this makes sense in the context of an actual login form which needs to redirect after login. For a RESTful web service however, the desired response for a successful authentication should be 200 OK.

This is done by injecting a custom authentication success handler in the form login filter, to replace the default one. The new handler implements the exact same login as the default org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler with one notable difference – the redirect logic is removed:

public class MySavedRequestAwareAuthenticationSuccessHandler 
      extends SimpleUrlAuthenticationSuccessHandler {

    private RequestCache requestCache = new HttpSessionRequestCache();

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, 
      Authentication authentication) throws ServletException, IOException {
        SavedRequest savedRequest = requestCache.getRequest(request, response);

        if (savedRequest == null) {
            clearAuthenticationAttributes(request);
            return;
        }
        String targetUrlParam = getTargetUrlParameter();
        if (isAlwaysUseDefaultTargetUrl() || 
          (targetUrlParam != null && 
          StringUtils.hasText(request.getParameter(targetUrlParam)))) {
            requestCache.removeRequest(request, response);
            clearAuthenticationAttributes(request);
            return;
        }

        clearAuthenticationAttributes(request);
    }

    public void setRequestCache(RequestCache requestCache) {
        this.requestCache = requestCache;
    }
}

3.5. The Authentication Manager and Provider

The authentication process uses an in-memory provider to perform authentication – this is meant to simplify the configuration as a production implementation of these artifacts is outside the scope of this post.

3.6 Finally – Authentication against the running REST Service

Now let’s see how we can authenticate against the REST API – the URL for login is /j_spring_security_check – and a simple curl command performing login would be:

curl -i -X POST -d j_username=user -d j_password=userPass
http://localhost:8080/spring-security-rest/j_spring_security_check

This request will return the Cookie which will then be used by any subsequent request against the REST Service.

We can use curl to authentication and store the cookie it receives in a file:

curl -i -X POST -d j_username=user -d j_password=userPass -c /opt/cookies.txt
http://localhost:8080/spring-security-rest/j_spring_security_check

Then we can use the cookie from the file to do further authenticated requests:

curl -i --header "Accept:application/json" -X GET -b /opt/cookies.txt 
http://localhost:8080/spring-security-rest/api/foos

This authenticated request will correctly result in a 200 OK:

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Wed, 24 Jul 2013 20:31:13 GMT

[{"id":0,"name":"JbidXc"}]

4. Maven and other trouble

The Spring core dependencies necessary for a web application and for the REST Service have been discussed in detail. For security, we’ll need to add: spring-security-web and spring-security-config – all of these have also been covered in the Maven for Spring Security tutorial.

It’s worth paying close attention to the way Maven will resolve the older Spring dependencies – the resolution strategy will start causing problems once the security artifacts are added to the pom. To address this problem, some of the core dependencies will need to be overridden in order to keep them at the right version.

5. Conclusion

This post covered the basic security configuration and implementation for a RESTful Service using Spring Security 3.1, discussing the web.xml, the security configuration, the HTTP status codes for the authentication process and the Maven resolution of the security artifacts.

The implementation of this Spring Security REST Tutorial can be found in the github project – this is an Eclipse based project, so it should be easy to import and run as it is.
 

Reference: Spring REST Service Security 3 from our JCG partner Eugen Paraschiv at the baeldung blog.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button