In the last two articles, I have demonstrated Spring Boot REST APIs through an example.
Furthermore, we have also seen how to validate bean properties using hibernate validators.
Particularly In this tutorial, we will see how to add secure our REST endpoints through spring security.
What is Spring Security
SpringSecurity
It is a part of the Spring project that allows securing applications.
Basically, it solves two problems:
Authentication: The process by which a user validates credentials against the system and acquires some roles.
Authorization: Process by which a user is given permission to access a resource. This will depend on the assigned roles.
For better understanding, let’s create a demo.
We will define two users in memory with their respective roles that will be loaded by the SpringSecurity
module.
By using those credentials we will perform the authentication and authorization process.
In the last tutorial, we have created a simple User Management System (UMS).
We have also exposed a few REST APIs to perform CRUD operation.
Let’s add Spring security on that project.
How to add Spring Security?
Steps to add Spring Security in Spring Boot Project
- Add
spring-boot-security-starter
andspring-security-test
in maven dependency. - Create a Security configuration file (
SpringSecurityConfiguration.java
) and configure spring security.
Maven Dependency for Spring Security
To work with spring security, you have to add two dependencies.
The first one is spring-boot-starter-security
and second is spring-security-test
.
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency>
Complete pom.xml
as below
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.6.RELEASE</version> <relativePath /> <! – lookup parent from repository --> </parent> <groupId>com.codedelay.rest</groupId> <artifactId>spring-boot-rest-security</artifactId> <version>0.0.1-SNAPSHOT</version> <name>spring-boot-rest-security</name> <description>Hello world example project for Spring Boot Security </description> <properties> <java.version>1.8</java.version> <maven-jar-plugin.version>3.1.1</maven-jar-plugin.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
Maven Dependency Tree
[INFO] – ----------< com.codedelay.rest:spring-boot-rest-security >------------ [INFO] Building spring-boot-rest-security 0.0.1-SNAPSHOT [INFO] – ------------------------------[ jar ]--------------------------------- [INFO] [INFO] - – maven-dependency-plugin:3.1.1:tree (default-cli) @ spring-boot-rest-security – - [INFO] com.codedelay.rest:spring-boot-rest-security:jar:0.0.1-SNAPSHOT [INFO] +- org.springframework.boot:spring-boot-starter-data-jpa:jar:2.1.6.RELEASE:compile [INFO] | +- org.springframework.boot:spring-boot-starter-aop:jar:2.1.6.RELEASE:compile [INFO] | | \- org.aspectj:aspectjweaver:jar:1.9.4:compile [INFO] | +- org.springframework.boot:spring-boot-starter-jdbc:jar:2.1.6.RELEASE:compile [INFO] | | +- com.zaxxer:HikariCP:jar:3.2.0:compile [INFO] | | \- org.springframework:spring-jdbc:jar:5.1.8.RELEASE:compile [INFO] | +- javax.transaction:javax.transaction-api:jar:1.3:compile [INFO] | +- javax.xml.bind:jaxb-api:jar:2.3.1:compile [INFO] | | \- javax.activation:javax.activation-api:jar:1.2.0:compile [INFO] | +- org.hibernate:hibernate-core:jar:5.3.10.Final:compile [INFO] | | +- org.jboss.logging:jboss-logging:jar:3.3.2.Final:compile [INFO] | | +- javax.persistence:javax.persistence-api:jar:2.2:compile [INFO] | | +- org.javassist:javassist:jar:3.23.2-GA:compile [INFO] | | +- net.bytebuddy:byte-buddy:jar:1.9.13:compile [INFO] | | +- antlr:antlr:jar:2.7.7:compile [INFO] | | +- org.jboss:jandex:jar:2.0.5.Final:compile [INFO] | | +- com.fasterxml:classmate:jar:1.4.0:compile [INFO] | | +- org.dom4j:dom4j:jar:2.1.1:compile [INFO] | | \- org.hibernate.common:hibernate-commons-annotations:jar:5.0.4.Final:compile [INFO] | +- org.springframework.data:spring-data-jpa:jar:2.1.9.RELEASE:compile [INFO] | | +- org.springframework.data:spring-data-commons:jar:2.1.9.RELEASE:compile [INFO] | | +- org.springframework:spring-orm:jar:5.1.8.RELEASE:compile [INFO] | | +- org.springframework:spring-context:jar:5.1.8.RELEASE:compile [INFO] | | +- org.springframework:spring-tx:jar:5.1.8.RELEASE:compile [INFO] | | +- org.springframework:spring-beans:jar:5.1.8.RELEASE:compile [INFO] | | \- org.slf4j:slf4j-api:jar:1.7.26:compile [INFO] | \- org.springframework:spring-aspects:jar:5.1.8.RELEASE:compile [INFO] +- org.springframework.boot:spring-boot-starter-web:jar:2.1.6.RELEASE:compile [INFO] | +- org.springframework.boot:spring-boot-starter:jar:2.1.6.RELEASE:compile [INFO] | | +- org.springframework.boot:spring-boot-starter-logging:jar:2.1.6.RELEASE:compile [INFO] | | | +- ch.qos.logback:logback-classic:jar:1.2.3:compile [INFO] | | | | \- ch.qos.logback:logback-core:jar:1.2.3:compile [INFO] | | | +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.11.2:compile [INFO] | | | | \- org.apache.logging.log4j:log4j-api:jar:2.11.2:compile [INFO] | | | \- org.slf4j:jul-to-slf4j:jar:1.7.26:compile [INFO] | | +- javax.annotation:javax.annotation-api:jar:1.3.2:compile [INFO] | | \- org.yaml:snakeyaml:jar:1.23:runtime [INFO] | +- org.springframework.boot:spring-boot-starter-json:jar:2.1.6.RELEASE:compile [INFO] | | +- com.fasterxml.jackson.core:jackson-databind:jar:2.9.9:compile [INFO] | | | +- com.fasterxml.jackson.core:jackson-annotations:jar:2.9.0:compile [INFO] | | | \- com.fasterxml.jackson.core:jackson-core:jar:2.9.9:compile [INFO] | | +- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.9.9:compile [INFO] | | +- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.9.9:compile [INFO] | | \- com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.9.9:compile [INFO] | +- org.springframework.boot:spring-boot-starter-tomcat:jar:2.1.6.RELEASE:compile [INFO] | | +- org.apache.tomcat.embed:tomcat-embed-core:jar:9.0.21:compile [INFO] | | +- org.apache.tomcat.embed:tomcat-embed-el:jar:9.0.21:compile [INFO] | | \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:9.0.21:compile [INFO] | +- org.hibernate.validator:hibernate-validator:jar:6.0.17.Final:compile [INFO] | | \- javax.validation:validation-api:jar:2.0.1.Final:compile [INFO] | +- org.springframework:spring-web:jar:5.1.8.RELEASE:compile [INFO] | \- org.springframework:spring-webmvc:jar:5.1.8.RELEASE:compile [INFO] | \- org.springframework:spring-expression:jar:5.1.8.RELEASE:compile [INFO] +- com.h2database:h2:jar:1.4.199:runtime [INFO] +- org.springframework.boot:spring-boot-starter-test:jar:2.1.6.RELEASE:test [INFO] | +- org.springframework.boot:spring-boot-test:jar:2.1.6.RELEASE:test [INFO] | +- org.springframework.boot:spring-boot-test-autoconfigure:jar:2.1.6.RELEASE:test [INFO] | +- com.jayway.jsonpath:json-path:jar:2.4.0:test [INFO] | | \- net.minidev:json-smart:jar:2.3:test [INFO] | | \- net.minidev:accessors-smart:jar:1.2:test [INFO] | | \- org.ow2.asm:asm:jar:5.0.4:test [INFO] | +- junit:junit:jar:4.12:test [INFO] | +- org.assertj:assertj-core:jar:3.11.1:test [INFO] | +- org.mockito:mockito-core:jar:2.23.4:test [INFO] | | +- net.bytebuddy:byte-buddy-agent:jar:1.9.13:test [INFO] | | \- org.objenesis:objenesis:jar:2.6:test [INFO] | +- org.hamcrest:hamcrest-core:jar:1.3:test [INFO] | +- org.hamcrest:hamcrest-library:jar:1.3:test [INFO] | +- org.skyscreamer:jsonassert:jar:1.5.0:test [INFO] | | \- com.vaadin.external.google:android-json:jar:0.0.20131108.vaadin1:test [INFO] | +- org.springframework:spring-core:jar:5.1.8.RELEASE:compile [INFO] | | \- org.springframework:spring-jcl:jar:5.1.8.RELEASE:compile [INFO] | +- org.springframework:spring-test:jar:5.1.8.RELEASE:test [INFO] | \- org.xmlunit:xmlunit-core:jar:2.6.2:test [INFO] +- org.springframework.boot:spring-boot-devtools:jar:2.1.6.RELEASE:runtime (optional) [INFO] | +- org.springframework.boot:spring-boot:jar:2.1.6.RELEASE:compile [INFO] | \- org.springframework.boot:spring-boot-autoconfigure:jar:2.1.6.RELEASE:compile [INFO] +- org.springframework.boot:spring-boot-starter-security:jar:2.1.6.RELEASE:compile [INFO] | +- org.springframework:spring-aop:jar:5.1.8.RELEASE:compile [INFO] | +- org.springframework.security:spring-security-config:jar:5.1.5.RELEASE:compile [INFO] | \- org.springframework.security:spring-security-web:jar:5.1.5.RELEASE:compile [INFO] \- org.springframework.security:spring-security-test:jar:5.1.5.RELEASE:test [INFO] \- org.springframework.security:spring-security-core:jar:5.1.5.RELEASE:compile
Now let’s take a look at UserController
.
As of now none of the APIs are secure. That means anyone can access user data or modify and even delete user details.
package com.codedelay.rest.controller; import javax.validation.Valid; import javax.validation.constraints.Min; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import com.codedelay.rest.entity.User; import com.codedelay.rest.service.UserManageService; @RestController @RequestMapping("/api/user") @Validated public class UserController { @Autowired private UserManageService mService; @GetMapping("/getAll") public Iterable<User> getAllUsers() { return mService.getAllUsers(); } @PostMapping("/add") @ResponseStatus(HttpStatus.CREATED) public User addUser(@Valid @RequestBody User user) { return mService.addUser(user); } @GetMapping("/find/{id}") public User findUserById(@PathVariable("id") @Min(1) int id) { return mService.findUserById(id); } @PutMapping("/update/{id}") public User addOrUpdateUserById(@RequestBody User user, @PathVariable("id") int id) { return mService.addOrUpdateUserById(user, id); } @DeleteMapping("/delete/{id}") public void deleteUser(@PathVariable("id") int id) { mService.deleteUser(id); } }
Now let’s configure security inside a configuration file.
Configuring Spring Security
The first thing we are going to do is configure our application using WebSecurityConfigurerAdapter
and then we will apply a basic security layer with user/password authentication.
WebSecurityConfigurerAdapter
is a class that allows customization to HttpSecurity.
At first, let create a file SpringSecurityConfiguration
and configure spring security.
Override two methods configure(AuthenticationManagerBuilder auth)
and configure(AuthenticationManagerBuilder auth)
package com.codedelay.rest.security; 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.WebSecurityConfigurerAdapter; @Configuration public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().withUser("root").password("{noop}linux").roles("USER", "ADMIN").and() .withUser("test").password("{noop}test123").roles("USER"); } @Override protected void configure(HttpSecurity http) throws Exception { http.httpBasic().and().authorizeRequests().antMatchers(HttpMethod.GET, "/api/user/**").hasRole("USER") .antMatchers(HttpMethod.POST, "/api/user").hasRole("ADMIN").antMatchers(HttpMethod.PUT, "/api/user/**") .hasRole("ADMIN").antMatchers(HttpMethod.PATCH, "/api/user/**").hasRole("ADMIN") .antMatchers(HttpMethod.DELETE, "/api/user/**").hasRole("ADMIN").and().csrf().disable().formLogin() .disable(); } }
There are several mechanisms to authenticate the user including JDBC authentication, LDAP authentication.
But for this tutorial, we are doing in-memory authentication.
For the authentication, we have added two users’ root and test.
Here root is a user as well as admin.
Whereas, the test is just a user.
After adding authentication, it is time to add some simple authorization on each URL using roles:
In the above codeconfigure(HttpSecurity http)
, we have specified that the only HTTP GET can be called by a user (test and root).
Whereas, HTTP POST, PUT, and DELETE can only be called by admin.
Let’s test our modifications using the PostMan.
Scenario – 1 When no authorization is provided
HTTP GET http://localhost:8080/api/user/getAll
{ "timestamp": "2019-07-31T13:16:19.163+0000", "status": 401, "error": "Unauthorized", "message": "Unauthorized", "path": "/api/user/getAll" }
Scenario -2 When ‘test’ credential provided
HTTP DELETE http://localhost:8080/api/user/getAll
{ "timestamp": "2019-07-31T13:20:12.755+0000", "status": 403, "error": "Forbidden", "message": "Forbidden", "path": "/api/user/getAll" }
Scenario – 3 When ‘root’ credential provided
HTTP DELETE http://localhost:8080/api/user/getAll
200 OK
Conclusion
In this tutorial, we have learned about spring security and how to add spring security in a spring boot project.