RSS

Stateless Spring Security on REST API

30 Jun

First I would like you to go through my previous blog post that I have written for Spring Security on REST Api. In the above spring security scenario based on state full mechanism. It is using the default user details service which is defined through the security.xml but we know that once we are going to develop real world application those use the custom user stores to store the user details so we need to plug those databases to our authentication process. Another thing that we know in the REST apis should be stateless so what I’m going to show you how to secure the REST Api with stateless basic authentication by using the custom user details service.

First of all you need to understand the flow of this security mechanism. See the following diagram.

Spring-Security

Lets look at the configuration and cording. I assume that you have clear idea about spring security configuration so I’m not going to explain each and every thing on this project. If you have doubt about the spring configurations please follow my previous post carefully.

webSecurityConfig.xml

<?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" create-session="stateless"
		entry-point-ref="restServicesEntryPoint" authentication-manager-ref="authenticationManagerForRest">
		<intercept-url pattern="/api/**" />
		<sec:form-login authentication-success-handler-ref="mySuccessHandler" />
		<sec:access-denied-handler ref="myAuthenticationAccessDeniedHandler" />
		<http-basic />
	</http>

	<!-- Entry point for REST service. -->
	<beans:bean id="restServicesEntryPoint"
		class="spring.security.custom.rest.api.security.RestAuthenticationEntryPoint" />

	<!-- Custom User details service which is provide the user data -->
	<beans:bean id="customUserDetailsService"
		class="spring.security.custom.rest.api.security.CustomUserDetailsService" />

	<!-- Connect the custom authentication success handler -->
	<beans:bean id="mySuccessHandler"
		class="spring.security.custom.rest.api.security.RestAuthenticationSuccessHandler" />

	<!-- Using Authentication Access Denied handler -->
	<beans:bean id="myAuthenticationAccessDeniedHandler"
		class="spring.security.custom.rest.api.security.RestAuthenticationAccessDeniedHandler" />

	<!-- Authentication manager -->
	<authentication-manager alias="authenticationManagerForRest">
		<authentication-provider user-service-ref="customUserDetailsService" />
	</authentication-manager>

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

</beans:beans>

Now you can focus on the http configuration in above xml. Within the http name tag you can see I have defined the http-basic that means this url should be secured by basic authentication. You have to send the username and password by Base64 encoding as follows.

admin:adminpass encoded by Base64 (YWRtaW46YWRtaW5wYXNz)

Second main point of this project is custom user detail service. As I mentioned earlier in the real world application you have to use the existing authentication source to do the authentication.

package spring.security.custom.rest.api.security;

import java.util.ArrayList;
import java.util.Collection;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

/**
 * CustomUserDetailsService provides the connection point to external data
 * source
 * 
 * @author malalanayake
 * 
 */
public class CustomUserDetailsService implements UserDetailsService {
	private String USER_ADMIN = "admin";
	private String PASS_ADMIN = "adminpass";

	private String USER = "user";
	private String PASS = "userpass";

	@Override
	public UserDetails loadUserByUsername(String authentication) throws UsernameNotFoundException {
		CustomUserData customUserData = new CustomUserData();
		// You can talk to any of your user details service and get the
		// authentication data and return as CustomUserData object then spring
		// framework will take care of the authentication
		if (USER_ADMIN.equals(authentication)) {
			customUserData.setAuthentication(true);
			customUserData.setPassword(PASS_ADMIN);
			Collection<CustomRole> roles = new ArrayList<CustomRole>();
			CustomRole customRole = new CustomRole();
			customRole.setAuthority("ROLE_ADMIN");
			roles.add(customRole);
			customUserData.setAuthorities(roles);
			return customUserData;
		} else if (USER.equals(authentication)) {
			customUserData.setAuthentication(true);
			customUserData.setPassword(PASS);
			Collection<CustomRole> roles = new ArrayList<CustomRole>();
			CustomRole customRole = new CustomRole();
			customRole.setAuthority("ROLE_USER");
			roles.add(customRole);
			customUserData.setAuthorities(roles);
			return customUserData;
		} else {
			return null;
		}
	}

	/**
	 * Custom Role class for manage the authorities
	 * 
	 * @author malalanayake
	 * 
	 */
	private class CustomRole implements GrantedAuthority {
		String role = null;

		@Override
		public String getAuthority() {
			return role;
		}

		public void setAuthority(String roleName) {
			this.role = roleName;
		}

	}

}

In the above code you can see I have implemented the UserDetailsService interface and override the method loadUserByUsername. Within this method you need to connect to the external user store and get the credentials and the roles associated with the user name. I have hardcoded the values for your understanding.

Another special thing is you need to pass the object which is implemented by the UserDetails so you can see I have created the following class for that.

package spring.security.custom.rest.api.security;

import java.util.ArrayList;
import java.util.Collection;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

/**
 * This class is provide the user details which is needed for authentication
 * 
 * @author malalanayake
 * 
 */
public class CustomUserData implements UserDetails {
	Collection<? extends GrantedAuthority> list = null;
	String userName = null;
	String password = null;
	boolean status = false;

	public CustomUserData() {
		list = new ArrayList<GrantedAuthority>();
	}

	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		return this.list;
	}

	public void setAuthorities(Collection<? extends GrantedAuthority> roles) {
		this.list = roles;
	}

	public void setAuthentication(boolean status) {
		this.status = status;
	}

	@Override
	public String getPassword() {
		return this.password;
	}

	public void setPassword(String pass) {
		this.password = pass;
	}

	@Override
	public String getUsername() {
		return this.userName;
	}

	@Override
	public boolean isAccountNonExpired() {
		return true;
	}

	@Override
	public boolean isAccountNonLocked() {
		return true;
	}

	@Override
	public boolean isCredentialsNonExpired() {
		return true;
	}

	@Override
	public boolean isEnabled() {
		return true;
	}

}

Finally we need to take care of the unauthenticated responses so there are two possibilities that we can throw the 401 Unauthorized response.

1. User come to access the service without proper authentication. Then the spring framework redirect the user to get the authentication but this is a REST api so we don’t need to redirect the user to get the authentication thats why we simply pass the 401 response in RestAuthenticationEntryPoint class.

package spring.security.custom.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 their authentication.
 * 
 * @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");

	}

}

2. Second possible scenario is user has proper authentication but he doesn’t have proper authorization that means he doesn’t have the proper ROLE. This scenario spring framework push the request to the RestAuthenticationAccessDeniedHandler then we need to simply pass the 401 Unauthorized response. If we didn’t set this handler the spring framework push the 403 Forbidden response.

package spring.security.custom.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.access.AccessDeniedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;

public class RestAuthenticationAccessDeniedHandler implements AccessDeniedHandler {

	@Override
	public void handle(HttpServletRequest request, HttpServletResponse response,
			AccessDeniedException arg2) throws IOException, ServletException {
		response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");

	}
}

I hope you will enjoy the spring security with REST Api. You can download the total source of this project from here.

Advertisements
 
12 Comments

Posted by on June 30, 2014 in java, spring

 

Tags: , ,

12 responses to “Stateless Spring Security on REST API

  1. Tin

    September 23, 2014 at 1:37 am

    What if you need a service to be public while the rest of the API is secured? Can you just bypass the entry point?

     
    • malalanayake

      September 26, 2014 at 3:41 pm

      Then you don’t need to make security over that API but rest of the API’s you have to configure in spring-security xml. I hope you got the answer.

       
  2. john

    November 6, 2014 at 5:10 am

    can you please tell me if there is some other way to execute your sample other than using crul

     
  3. ishan

    December 31, 2014 at 12:55 pm

    tried this.. authentication working only for j_spring_security_check page…
    for others throwing 401…

     
  4. Don

    January 28, 2015 at 11:04 am

    Any suggestions on how to combine this approach with Oauth2 ?

     
  5. tiagomac

    April 14, 2015 at 5:46 am

    Thank you. You saved my day

     
  6. robwinch

    July 16, 2015 at 4:40 pm

    Typically a good pattern is to expose the user information and future credentials via a RESTful endpoint. This pattern works very easily with Spring Session. See http://docs.spring.io/spring-session/docs/current/reference/html5/guides/rest.html

     
  7. Naveen P.G

    January 16, 2016 at 3:16 pm

    how to test this funtionality? I downloaded this project and imported to STS but I m unable to figure how to start testing your project!

     
  8. Ananth

    January 22, 2016 at 7:04 am

    can you let us know how to test your sample

     
  9. Sahan

    January 28, 2016 at 2:11 pm

    I get every time unauthorized. (http://localhost:8080/spring.security.custom.rest.api/api/customer)
    How can we get authorization for the contenet,
    http://localhost:8080/spring.security.custom.rest.api/api/customer?username=admin&amp; password=adminpass

     
  10. arun

    April 15, 2016 at 10:05 am

    it is not going to success handler… why?

     
  11. Abhijit

    August 11, 2016 at 11:02 am

    if some one stolen my token then how we will handle the security in rest Api

     

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

 
%d bloggers like this: