Spring Security with Spring Boot
What is Spring Security
Spring Security is a powerful and customizable access-control framework. It is the recommended framework for securing Spring-based applications. Spring Security is mainly focuses on managing both authentication and authorization to Java applications. The actual benefit and power of Spring Security is that it can be extended to meet any custom requirements.
How to use Spring Security
In this article you will learn how to add a basic authentication with spring security to spring boot application. As for the database we are using Postgres and IDE is Intelij, you can use any other database or IDE you are familiar with for the developments.
Follow below instructions to create a new spring project with springs security.
Lets create a new project first. Click on File -> New -> Project. Select Spring Initializer and Click on Next.
Fill the project related details as below and click on next
From the Dependencies menu, search and add below mentioned packages.
Spring Web : provides the Web tools required to create Rest API’s
Spring Security : provides the security tools for spring applications
Spring Data JPA : required to manage persistence
Click on next and select the location you need to save your project
Lets first create a database table “user” as below.
CREATE TABLE "user" (
usr_id bigserial NOT NULL,
usr_status int4 NOT NULL,
usr_account_expired bool NULL,
usr_account_locked bool NULL,
usr_contact_no varchar(20) NULL,
usr_credentials_expired bool NULL,
usr_email varchar(250) NOT NULL,
usr_enabled bool NULL,
usr_first_name varchar(255) NULL,
usr_password varchar(250) NOT NULL,
usr_second_name varchar(255) NULL,
usr_username varchar(50) NOT NULL,
usr_root_enable bool null,
CONSTRAINT user_pkey PRIMARY KEY (usr_id)
);
Insert a record for the user table with below insert statement. This will be our authenticated user to access the endpoints.
INSERT INTO "user" (usr_status,usr_account_expired,usr_account_locked,usr_contact_no,usr_credentials_expired,usr_email,usr_enabled,usr_first_name,usr_password,usr_second_name,usr_username,usr_root_enable) VALUES
(2,false,false,'0111222222',false,'admin@techprogramme.com',true,'Alex','{bcrypt}$2a$10$1XPGeFuOcVnRguiv1f/zq.EQygdL3CW8Ie.wBNfycMo0JfUN7u/3q','Joe','admin',true)
;
Open the pom.xml file and you will be able to see that the required dependencies are applied to the pom 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 https://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.4.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.springsecurity</groupId>
<artifactId>demospringsecurity</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demospringsecurity</name>
<description>Spring Security Sample Project</description>
<properties>
<java.version>8</java.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>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.2.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Open the application.properties file under the resources and change it as below. When changing make sure to change the database details related to your postgres database connection
spring.datasource.platform=postgres
spring.datasource.url= jdbc:postgresql://localhost:5432/testdb?currentSchema=public
spring.datasource.username=postgres
spring.datasource.password=root
Create a new package called controller and add a class named MyController. Once the class created change the class as below.
@RestController will mark the class as a Rest Endpoint and @RequestMapping(“myController”) will be the endpoint name for the service.
@GetMapping(“/hello”) is the endpoint for the method.
package com.springsecurity.demospringsecurity.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("myController")
public class MyController {
@GetMapping("/hello")
public String helloWorld(){
return "hello world";
}
}
Create a package called entity and add a new class named “User”. This will be the entity class for user table and change it as below
package com.springsecurity.demospringsecurity.entity;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import javax.persistence.*;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "user", schema = "public")
public class User implements UserDetails {
private Integer userSeq;
private String username;
private String password;
private String email;
private String contactNo;
private String firstName;
private String secondName;
private boolean enabled;
private boolean accountNonLocked;
private boolean accountNonExpired;
private boolean credentialsNonExpired;
private Set<GrantedAuthority> authorities = new HashSet<>();
private Boolean rootEnable;
@Id
@Column(name = "usr_id", nullable = false, precision = 0, unique = true)
public Integer getUserSeq() {
return userSeq;
}
public void setUserSeq(Integer userSeq) {
this.userSeq = userSeq;
}
@Basic
@Column(name = "usr_username", nullable = false, length = 50)
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Basic
@Column(name = "usr_password", nullable = false, length = 250)
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Basic
@Column(name = "usr_email", nullable = false, length = 250)
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Basic
@Column(name = "usr_contact_no", length = 20)
public String getContactNo() {
return contactNo;
}
public void setContactNo(String contactNo) {
this.contactNo = contactNo;
}
@Basic
@Column(name = "usr_first_name")
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
@Basic
@Column(name = "usr_second_name")
public String getSecondName() {
return secondName;
}
public void setSecondName(String secondName) {
this.secondName = secondName;
}
@Basic
@Column(name = "usr_enabled")
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
@Basic
@Column(name = "usr_account_locked")
public boolean isAccountNonLocked() {
return !accountNonLocked;
}
public void setAccountNonLocked(boolean accountNonLocked) {
this.accountNonLocked = accountNonLocked;
}
@Basic
@Column(name = "usr_account_expired")
public boolean isAccountNonExpired() {
return !accountNonExpired;
}
public void setAccountNonExpired(boolean accountNonExpired) {
this.accountNonExpired = accountNonExpired;
}
@Basic
@Column(name = "usr_credentials_expired")
public boolean isCredentialsNonExpired() {
return !credentialsNonExpired;
}
public void setCredentialsNonExpired(boolean credentialsNonExpired) {
this.credentialsNonExpired = credentialsNonExpired;
}
@Transient
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
public void setAuthorities(Set<GrantedAuthority> authorities) {
this.authorities = authorities;
}
@Basic
@Column(name = "usr_root_enable")
public Boolean getRootEnable() {
return rootEnable;
}
public void setRootEnable(Boolean rootEnable) {
this.rootEnable = rootEnable;
}
}
Now lets create a repository for the User entity. Create a new package called repository and add an interface named UserRepository.
Change the created interface as below.
package com.springsecurity.demospringsecurity.repository;
import com.springsecurity.demospringsecurity.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<User, Integer> {
Optional<User> findByUsernameIgnoreCase(String input);
}
Create a new package named serviceImpl and add a new class called CustomUserDetailsService. This will be a implementation class for UserDetailsService which is used to retrieve the user’s authorization information. Change the CustomUserDetailsService class as below.
@Service(value = “userDetailsService”) will mark the class as a service and here we have provided a value for the service. implements UserDetailsService will implements the methods required to authenticate. loadUserByUsername method will check if the user is available or not. if user is available it will set the required authorities to the user and will return User object. As for this example we have added a single authority called “ADMIN”. normally in a production environment authorities also retrieved from the database.
package com.springsecurity.demospringsecurity.serviceImpl;
import com.springsecurity.demospringsecurity.entity.User;
import com.springsecurity.demospringsecurity.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AccountStatusUserDetailsChecker;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Service;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
@Service(value = "userDetailsService")
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
@Autowired
public CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String input) {
Optional<User> user;
user = this.userRepository.findByUsernameIgnoreCase(input);
if (!user.isPresent()) {
throw new BadCredentialsException("Bad credentials");
}
new AccountStatusUserDetailsChecker().check(user.get());
user.get().setAuthorities(this.getAuthorities());
return user.get();
}
Set<GrantedAuthority> getAuthorities() {
Set<GrantedAuthority> authorities = new HashSet<>();
authorities.add(new SimpleGrantedAuthority("ADMIN"));
return authorities;
}
}
Create a new class called SpringSecurityConfig. This class will inherit the WebSecurityConfigurerAdapter and so we can customize both WebSecurity and HttpSecurity as we required.
Once the class is created, change it as below
package com.springsecurity.demospringsecurity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
import java.util.Collections;
@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
private AuthenticationEntryPoint authenticationEntryPoint;
private final UserDetailsService userDetailsService;
@Autowired
public SpringSecurityConfig(UserDetailsService userDetailsService,
AuthenticationEntryPoint authenticationEntryPoint) {
this.userDetailsService = userDetailsService;
this.authenticationEntryPoint = authenticationEntryPoint;
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().anyRequest().authenticated().and().httpBasic().authenticationEntryPoint(authenticationEntryPoint);
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Collections.singletonList("*"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("authorization", "content-type", "x-auth-token"));
configuration.setExposedHeaders(Collections.singletonList("x-auth-token"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
Create a class called AuthenticationEntryPointConfig which inherits the BasicAuthenticationEntryPoint and change it as below
package com.springsecurity.demospringsecurity;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@Component
public class AuthenticationEntryPointConfig extends BasicAuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
response.addHeader("WWW-Authenticate","Basic Relam - " + getRealmName());
response.getHeader("Authorization");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
PrintWriter writer = response.getWriter();
writer.println("HTTP Status 401 - " + authException.getMessage());
}
@Override
public void afterPropertiesSet() {
setRealmName("POSXAuth");
super.afterPropertiesSet();
}
}
Once above steps are completed run the application. You can use postman to check if the services are working correctly with the authentications. You can add username and password to the authorization as below. If you need you can send username and password as a token by converting “admin:123456”(username:password) to base64. Once you converted the username and password combination to base64 you can send it with the header like “Authorization: Basic YWRtaW46MTIzNDU2”
Download the source code from GitHub