RSS

Spring Security on REST API

27 Jun

I think this post will be good who are working in REST api development. If you are in trouble with the security on REST api this will be really helpful to solve the problems.

Screen Shot 2014-06-26 at 5.09.07 PM

In above project structure I would like to explain the web.xml configuration as follows.

<?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" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
	xsi:schemaLocation="

http://java.sun.com/xml/ns/javaee


http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"

	id="WebApp_ID" version="3.0">

	<display-name>Spring MVC Application</display-name>
        <session-config>
		<session-timeout>1</session-timeout>
	</session-config>

	<!-- Spring root -->
	<context-param>
		<param-name>contextClass</param-name>
		<param-value>
         org.springframework.web.context.support.AnnotationConfigWebApplicationContext
      </param-value>
	</context-param>
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>spring.security.rest.api</param-value>
	</context-param>

	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<!-- Spring child -->
	<servlet>
		<servlet-name>api</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>api</servlet-name>
		<url-pattern>/api/*</url-pattern>
	</servlet-mapping>

	<!-- Spring Security -->
	<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>

</web-app>

1. Define the spring root configuration.

<!-- Spring root -->
	<context-param>
		<param-name>contextClass</param-name>
		<param-value>
         org.springframework.web.context.support.AnnotationConfigWebApplicationContext
      </param-value>
	</context-param>
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>spring.security.rest.api</param-value>
	</context-param>

	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

In the above code snippet you can see I have define the “contextConfigLocation” parameter which is pointing the “spring.security.rest.api” this would be the initialization point of configuration. So you have to make sure you give the correct package name where the spring configuration is located.

2. Servlet mapping configuration

<!-- Spring child -->
	<servlet>
		<servlet-name>api</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>api</servlet-name>
		<url-pattern>/api/*</url-pattern>
	</servlet-mapping>

This is the point that you have to manage your url. you can give what you want as a url and it will expose the defined apis followed by the above url.
ex/ http://localhost:8080/spring.security.rest.api/api/customer

3. Spring security configuration

<!-- Spring Security -->
	<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>

you need to exactly define the filter-name as “springSecurityFilterChain” and as a good practice we are defining the url pattern as “/*” even our api starts at “/api/*” because then we can control the whole domain when its required.

Now I would like to go for the most important part of this project that is Spring security configuration. Lets see the webSecurityConfig.xml which is located at class path.

<?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.2.xsd


http://www.springframework.org/schema/beans


http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">

	<!-- Rest authentication entry point configuration -->
	<http use-expressions="true" entry-point-ref="restAuthenticationEntryPoint">
		<intercept-url pattern="/api/**" />
		<sec:form-login authentication-success-handler-ref="mySuccessHandler"
			authentication-failure-handler-ref="myFailureHandler" />

		<logout />
	</http>

	<!-- Connect the custom authentication success handler -->
	<beans:bean id="mySuccessHandler"
		class="spring.security.rest.api.security.RestAuthenticationSuccessHandler" />
	<!-- Using default failure handler -->
	<beans:bean id="myFailureHandler"
		class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler" />

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

	<!-- Enable the annotations for defining the secure role -->
	<global-method-security secured-annotations="enabled" />

</beans:beans>

In the above xml file I have defined the entry point as “restAuthenticationEntryPoint” with the success and failure handler what it means, in the spring context entry point is used to redirect the non authenticated request to get the authentication. In REST Api point of view this entry point is doesn’t make sense. As an example If the request comes without the authentication cookie application is not going to redirect the request to get the authentication rather sending the response as 401 Unauthorized.

package spring.security.rest.api.security;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.util.StringUtils;

/**
 * This will call once the request is authenticated. If it is not, the request
 * will be redirected to authenticate entry point
 * 
 * @author malalanayake
 * 
 */
public class RestAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
	private RequestCache requestCache = new HttpSessionRequestCache();

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

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

		clearAuthenticationAttributes(request);

		// Use the DefaultSavedRequest URL
		// final String targetUrl = savedRequest.getRedirectUrl();
		// logger.debug("Redirecting to DefaultSavedRequest Url: " + targetUrl);
		// getRedirectStrategy().sendRedirect(request, response, targetUrl);
	}

	public void setRequestCache(final RequestCache requestCache) {
		this.requestCache = requestCache;
	}
}
package spring.security.rest.api.security;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

/**
 * This entry point is called once the request missing the authentication but if
 * the request dosn't have the cookie then we send the unauthorized response.
 * 
 * @author malalanayake
 * 
 */
@Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {

	@Override
	public void commence(HttpServletRequest arg0, HttpServletResponse arg1,
			AuthenticationException arg2) throws IOException, ServletException {
		arg1.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");

	}

}

Spring-Security

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

	<!-- Enable the annotations for defining the secure role -->
	<global-method-security secured-annotations="enabled" />

Above xml snippet is represented the authentication manager configuration. Here I have used the default authentication manager which is coming with the spring security framework but in the realtime application this authentication manager should be custom and it should be provided the user authentication with existing database. I’ll discuss the custom authentication manager configuration in different blog post.

With the default authentication manager you need to define the users in this xml. You can see here I have defined the two users with the different roles. Make sure that you have configure the “global-method-security” because this is the tag that we are going to say that security roles configuration on resources is in annotation otherwise annotations will be ignored.

Now I’m going to explain the SpringSecurityConfig.java class. This is the class that we are exposing the security configurations to the spring framework.

package spring.security.rest.api;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;


/**
 * Expose the Spring Security Configuration
 * 
 * @author malalanayake
 * 
 */
@Configuration
@ImportResource({ "classpath:webSecurityConfig.xml" })
@ComponentScan("spring.security.rest.api.security")
public class SpringSecurityConfig {

	public SpringSecurityConfig() {
		super();
	}

}

The following class WebConfig.java is the one which is going to expose the rest endpoint. We need ti always point the api implementation package in component scan annotation.

package spring.security.rest.api;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

/**
 * Web Configuration expose the all services
 * 
 * @author malalanayake
 * 
 */
@Configuration
@ComponentScan("spring.security.rest.api.service")
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

	public WebConfig() {
		super();
	}

}

Finally I would like to explain the following service class

package spring.security.rest.api.service;

import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.http.MediaType;
import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.util.UriComponentsBuilder;

import spring.security.rest.api.entity.CustomerDetails;

import com.google.common.collect.Lists;

/**
 * Customer details exposing as a service. This is secured by spring role base
 * security. This service is only for ROLE_ADMIN
 * 
 * @author malalanayake
 * 
 */
@Controller
@RequestMapping(value = "/customer")
@Secured("ROLE_ADMIN")
public class CustomerDetailService {

	@Autowired
	private ApplicationEventPublisher eventPublisher;

	public CustomerDetailService() {
		super();
	}

	@RequestMapping(value = "/{id}", method = RequestMethod.GET, consumes = { MediaType.APPLICATION_JSON_VALUE })
	@ResponseBody
	public CustomerDetails findById(@PathVariable("id") final Long id,
			final UriComponentsBuilder uriBuilder, final HttpServletResponse response) {
		return new CustomerDetails(randomAlphabetic(6));
	}

	@RequestMapping(method = RequestMethod.GET, consumes = { MediaType.APPLICATION_JSON_VALUE })
	@ResponseBody
	public List<CustomerDetails> findAll() {
		return Lists.newArrayList(new CustomerDetails(randomAlphabetic(6)));
	}

}

You can see I have defined the secure role on top of the class that means this api is going to be available only for who has permission of ROLE_ADMIN.

Lets go to look at how to actually work this web service. First of all you need to build this application and run on the tomcat. Then open the command line and do the following curl command to get the cookie.

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

“j_spring_security_check” is default web service that expose from spring framework to get the authentication cookie.

You need to send the username and password as “j_username” and “j_password” parameters. You can see I have used the username and password which has ROLE_ADMIN. finally it will return the session information and it will be saved in cookies.txt

Request-Authentication

Now you can access the service as follows.

curl -i -H “Content-Type:application/json” -X GET -b ./cookies.txt http://localhost:8080/spring-security-rest-api/api/customer

Acess-the-service copy

Now think about the negative scenario. If you going to access the service without proper authentication you will get 401 Unauthorized response.

curl -i -H “Content-Type:application/json” -X GET http://localhost:8080/spring-security-rest-api/api/customer

Unauthorized-2 copy

You can download total project from here

About these ads
 
9 Comments

Posted by on June 27, 2014 in java, Other, spring

 

Tags: , ,

9 responses to “Spring Security on REST API

  1. nikita

    July 2, 2014 at 12:06 pm

    HI
    I tried running this project.
    I am able to run first curl command and the cookies file is getting generated.
    But on running the second command it says “bad request” and tomcat shows “error parsing http request header.”
    Kindly tell how to proceed.
    Thanks

     
    • malalanayake

      July 2, 2014 at 1:12 pm

      Can you check whether your request has any additional space or newline?
      as well as check the double quotation marks are correct in -H “Content-Type:application/json”

       
    • malalanayake

      July 2, 2014 at 1:16 pm

      If you are directly coping this command you can see the double quotation marks are different than the normal double quotation you need to replace the correct double quotation marks and invoke.

       
    • Carlos Araujo

      August 20, 2014 at 9:46 am

      Hi. I had the same error and solved by separating “Content-Type: application/json”
      Put a “space” between “Content-Type:” and ” application/json”.
      I use curl version 7.33.0 for Windows 7

       
  2. Máy đo đường huyết giá rẻ

    July 30, 2014 at 1:14 pm

    Me too. I have a error about request.
    How to fix it ?

     
  3. Shahar

    July 30, 2014 at 4:10 pm

    Rest should not have a login form…

     

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
Follow

Get every new post delivered to your Inbox.

%d bloggers like this: