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.
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:
- Add Spring Security dependency in project’s pom.xml
- Update database to store users credentials and roles
- Update DAO layer of application by adding required model and DAO repositories
- Implementation of Spring Security’s UserDetailsService to validate user credentials
- 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.
RESTful Web Service CRUD Operations with Spring Boot
RESTful Web Services Unit Testing with Spring Boot
RESTful Web Services Integration Testing with Spring Boot
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
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:
2. Access REST API with wrong credentials:
3. With correct credentials:
4. POST operation with “USER” role:
5. POST operation with “ADMIN” role:
Source Code
The complete source code is available at GitHub