RESTful Web Services Authentication and Authorization


This article shows an example of how to implement security in RESTful Web Services with basic authentication and authorization. It uses HTTP basic authentication and defines role-based access for HTTP Request methods. User credentials are stored in the database and Spring Security is used to implement the security.

Advertisements

Requirements

This example uses existing RESTful Web Services explained in RESTful Web Service CRUD Operations with Spring Boot. We are going implement security for these REST APIs. Security requirements are as follows:

  • User credentials should be retrieved from database
  • A user can have role as “USER” or “ADMIN”
  • Authentication: All RESTful Services should be accessible only to authenticated users
  • Authorization: Operations which involves modification(POST, PUT, DELETE Request methods) in database should only accessible to users with “ADMIN” role

Now let’s take a look at some higher level steps to follow to implement these requirements.

Steps to add Security in RESTful Web Services

Below are the higher level steps to add security in RESTful Web Services for above requirements:

  1. Add Spring Security dependency in project’s pom.xml
  2. Update database to store users credentials and roles
  3. Update DAO layer of application by adding required model and DAO repositories
  4. Implementation of Spring Security’s UserDetailsService to validate user credentials
  5. Configure security by adding a new class which extends WebSecurityConfigurerAdapter
    • Configure HTTP Basic authentication
    • Define URL patterns and HTTP Request methods for role-based access
    • Configure AuthenticationEntryPoint to set response on failed authentication
    • Set Session creation policy

Note: HTTP Basic authentication sets the username and password in request header encoded with Base64. For production environment, it should be used along with HTTPS.

Now check out all the required steps in details in below sections. You will get the complete code at the end of this article.

Technology Stack

Technology stack used in this example is:

  • Spring Boot 1.4.1.RELEASE
  • Spring Security 4
  • Spring Data JPA
  • Database – PostgreSQL
  • JDK 8
Advertisements

Update Project Dependency

Our RESTful Web Service project uses Spring Boot, so we will be using Spring Security dependencies provided by Spring Boot. We just need to add one dependency mentioned below in our pom.xml:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>

 

Update DAO Layer

Since we are storing users in the database we need to add a new table and create its Entity class and DAO repository. Our application is configured to create a database schema from Entity classes, so we just need to define the Entity class with required annotations. Our “Users” table has a simple structure with just three columns namely username, password, and role. As Spring Data JPA is used for this project, the repository for Users table will simply an interface extending the JpaRepository.

Entity Class:

package com.bytestree.restful.model;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

/**
 * 
 * @author BytesTree
 *
 */
@Entity
@Table(name = "users")
public class Users implements Serializable {

	private static final long serialVersionUID = 1948638898199176136L;
	
	@Id
	@Column(name = "username", unique = true, nullable = false, length = 100)
	private String username;

	@Column(name = "password", nullable = false, length = 100)
	private String password;

	@Column(name = "role", nullable = false, length = 100)
	private String role;
	
	public  Users() {	
	}
	
	public Users(String username, String password, String role) {
		this.username = username;
		this.password = password;
		this.role = role;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

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

	public String getRole() {
		return role;
	}

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

DAO Repository:

package com.bytestree.restful.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.bytestree.restful.model.Users;

@Repository
public interface UsersRepository extends JpaRepository<Users, String> {

}

 

Implement UserDetailsService

UserDetailsService is an interface provided by Spring security. Authentication of the user based on provided credentials is performed by this class. We need to implement loadUserByUsername() method which returns UserDetails object for valid credentials.

package com.bytestree.restful.service;

import java.util.Arrays;
import java.util.List;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import com.bytestree.restful.model.Users;
import com.bytestree.restful.repository.UsersRepository;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

	@Autowired
	UsersRepository usersRepository;

	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		Users user = usersRepository.findOne(username);

		if (user == null) {
			throw new UsernameNotFoundException("Invalid username or password");
		}

		return new User(username, user.getPassword(), true, true, true,
				true, AuthorityUtils.createAuthorityList(user.getRole()));

	}
	

	/**
	 * Add some users at application startup for testing
	 */
	@PostConstruct
	public void loadUsers() {
		List<Users> users = Arrays.asList(
							new Users("user", "password", "USER"),
							new Users("admin", "password", "ADMIN"));
		usersRepository.save(users);
	}

}

 

The loadUsers() method with @PostConstruct annotation will add some users(one with each role) for testing the application. No need to add this to your application.

 

Configure Authentication and Authorization

Finally the core security part. To implement authentication and authorization we need to create a sub class of WebSecurityConfigurerAdapter and override two configure methods. One with AuthenticationManagerBuilder as argument which hooks up the UserDetailsService and another with HttpSecurity as argument which defines the behavior of security. The later one performs the major role. Here we define that we need to use HTTP Basic authentication, only authenticated users should have access, which role should have which access and session creation policy.

For authorization we need to define which HTTP Request Method should be accessible for which role using hasAnyAuthority() method. In our example POST, PUT and DELETE Request Methods should be accessible to users with ADMIN role.

anyRequest().authenticated() defines that only authenticated users should able to access these services.

For AuthenticationEntryPoint we are going the use BasicAuthenticationEntryPoint provided by Spring as it satisfies our need by setting appropriate error message and header on failed authentication. If required, you can customize it’s behavior by sub-classing it.

We don’t have any requirement to keep the user session, so Stateless session creation policy is defined.

Let’s take a look at the code of this class:

package com.bytestree.restful.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;

/**
 * 
 * @author BytesTree
 *
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	
	private static String REALM_NAME ="RESTFUL_REALM";
	
	@Autowired
	private UserDetailsService userDetailsService;
	
	@Override
	public void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.userDetailsService(userDetailsService);
	}
	
	@Override
	protected void configure(HttpSecurity http) throws Exception { 
	  http.csrf().disable()
	  	.authorizeRequests()
	  	.antMatchers(HttpMethod.POST, "/employee/**").hasAnyAuthority("ADMIN")
	  	.antMatchers(HttpMethod.PUT, "/employee/**").hasAnyAuthority("ADMIN")
	  	.antMatchers(HttpMethod.DELETE, "/employee/**").hasAnyAuthority("ADMIN")
	  	.anyRequest().authenticated()
		.and().httpBasic()
		.realmName(REALM_NAME).authenticationEntryPoint(getBasicAuthEntryPoint())
		.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
	  }
	
	
	@Bean
	public BasicAuthenticationEntryPoint getBasicAuthEntryPoint(){
		BasicAuthenticationEntryPoint basicAuthEntryPoint = new BasicAuthenticationEntryPoint();
		basicAuthEntryPoint.setRealmName(REALM_NAME);
		return basicAuthEntryPoint;
	}

}

 

Testing

Test the application using Postman chrome app:

1. Access REST API without providing credentials:

RestfulSecurityNoCredentials

 

2. Access REST API with wrong credentials:

RestfulSecurityBadCredentials

 

3. With correct credentials:

RestfulSecurityCorrectCredentials

 

4. POST operation with “USER” role:

RestfulSecurityNoAuthority

 

5. POST operation with “ADMIN” role:

RestfulSecurityValidAuthority

Advertisements

Source Code

The complete source code is available at GitHub