Spring Security – Customizing Authentication and Authorization
Last Updated :
23 Sep, 2024
Spring Security is the powerful and customizable framework that provides the authentication, authorization, and other security features for the Java applications, especially the Spring-based ones. When building secure applications, controlling access to resources is important. Customizing authorization helps control access to specific endpoints or methods, ensuring that only authorized users can perform certain actions.
- Customizing authentication allows developers to define how users are authenticated.
- Customizing authorization helps control access to various resources based on user roles or permissions.
In this article, we will cover how to configure authentication and authorization in Spring Boot with some customization examples.
Customizing Authentication and Authorization in Spring Security
Spring Security provides flexible mechanisms for authentication (validating the identity of the user) and authorization (determining what resources or actions the authenticated user is permitted to access). While it offers standard login and access control by default, real-world applications often require customization, such as:
- Fetching user details from a database or external system.
- Handling multiple roles and permissions.
- Providing custom login and registration mechanisms.
- Integrating OAuth, JWT, or other advanced security protocols.
Authentication
Authentication verifies the identity of a user, typically done by checking the username and password in Spring Security. The framework supports multiple authentication methods, including in-memory, database, LDAP, OAuth, etc.
Key Components of Authentication:
UserDetailsService
: Retrieves user-related data and has the method loadUserByUsername()
to locate users by their username.AuthenticationManager
: Manages authentication and delegates the logic to AuthenticationProvider
.AuthenticationProvider
: Responsible for performing authentication by verifying user credentials.PasswordEncoder
: Handles password encryption. The common implementation is BCryptPasswordEncoder
, which provides hashing for secure password storage.
Authorization
Authorization determines access to resources based on roles or permissions after authentication. Once authenticated, a user's access is controlled based on assigned roles (e.g., ROLE_USER
, ROLE_ADMIN
).
Common Types of Authorization:
- Role-based authorization: Permissions are assigned based on roles.
- Method-level authorization: Restricts access to specific methods or endpoints based on roles or custom access policies.
Spring Security authorization mechanism includes:
- Access Control via URL Patterns: By defining the security rules in the configuration class (HttpSecurity), we can control which URL paths require the certain roles or privileges.
- Role Hierarchy: Spring allows us to define the hierarchy for the roles (e.g., ROLE_ADMIN can access everything that ROLE_USER can).
Key Components of Authorization:
HttpSecurity
: Defines URL access rules and what roles or permissions are needed.@PreAuthorize
and @Secured
Annotations: Used for method-level access control.AccessDecisionManager
: Makes final authorization decisions after evaluating user roles.
Customizing Authentication and Authorization in Spring Security
Step 1: Create a New Spring Boot Project
In IntelliJ IDEA, create a new Spring Boot project with the following options:
- Name:
spring-security-custom-auth
- Language: Java
- Type: Maven
- Packaging: Jar
Click on the Next button.
Step 2: Add Dependencies
Add the following dependencies in pom.xml
:
- Spring Web
- Spring Security
- Lombok
- Spring DevTools
- Spring Data JPA
- MySQL Driver
Click on the Create button.
Project Structure
After project creation done, the folder structure will look like the below image:
Step 3: Configure Application Properties
Add the following MySQL and Hibernate configuration in application.properties
:
# Application name
spring.application.name=spring-security-custom-auth
# MySQL Database Configuration
spring.datasource.url=jdbc:mysql://localhost:3306/exampledb?useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=mypassword
# Hibernate JPA Configuration
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect
spring.jpa.show-sql=true
Step 4: Create the User Class
Java
package com.gfg.springsecuritycustomauth.model;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Set;
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // Auto-generates the ID for the user
private Long id;
private String username; // Stores the username of the user
private String password; // Stores the encrypted password
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "user_roles", // Defines the join table for user and role mapping
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
private Set<Role> roles; // Set of roles associated with the user
}
Step 5: Create the Role Class
Java
package com.gfg.springsecuritycustomauth.model;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
Step 6: Create the UserRepository Interface
Java
package com.gfg.springsecuritycustomauth.repository;
import com.gfg.springsecuritycustomauth.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
}
Step 7: Create the RoleRepository Interface
Java
package com.gfg.springsecuritycustomauth.repository;
import com.gfg.springsecuritycustomauth.model.Role;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface RoleRepository extends JpaRepository<Role, Long> {
}
Step 8: Create the SecurityConfig Class
Java
package com.gfg.springsecuritycustomauth.config;
import com.gfg.springsecuritycustomauth.service.CustomUserDetailsService;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private CustomUserDetailsService userDetailsService;
// BCryptPasswordEncoder Bean to be used in password encoding
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// Define the security filter chain for handling HTTP security rules
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// Define which requests require authentication and authorization
http
.csrf().disable() // CSRF protection is disabled for simplicity, reconsider enabling it in production
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/admin/**").hasRole("ADMIN") // Only ADMIN role can access /admin/*
.requestMatchers("/user/**").hasRole("USER") // Only USER role can access /user/*
.requestMatchers("/public/**").permitAll() // Public endpoints that don't require authentication
.anyRequest().authenticated() // All other requests require authentication
)
.formLogin(form -> form
.loginPage("") // Custom login page
.permitAll() // Allow anyone to access the login page
)
.logout(logout -> logout
.permitAll() // Allow logout without restriction
);
return http.build(); // Return the configured SecurityFilterChain
}
// AuthenticationManager for custom user details service and password encoding
@Bean
public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
AuthenticationManagerBuilder authManagerBuilder =
http.getSharedObject(AuthenticationManagerBuilder.class);
// Configure custom UserDetailsService and password encoder
authManagerBuilder
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
return authManagerBuilder.build(); // Build and return the AuthenticationManager
}
}
Step 9: CustomUserDetailsService Class
Java
package com.gfg.springsecuritycustomauth.service;
import com.gfg.springsecuritycustomauth.model.User;
import com.gfg.springsecuritycustomauth.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
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.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.stream.Collectors;
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
return new org.springframework.security.core.userdetails.User(user.getUsername(),
user.getPassword(),
getAuthorities(user));
}
private Collection<? extends GrantedAuthority> getAuthorities(User user) {
return user.getRoles().stream()
.map(role -> new SimpleGrantedAuthority(role.getName()))
.collect(Collectors.toList());
}
}
Step 10: Create a DataSeeder Class
Java
package com.gfg.springsecuritycustomauth;
import com.gfg.springsecuritycustomauth.model.Role;
import com.gfg.springsecuritycustomauth.model.User;
import com.gfg.springsecuritycustomauth.repository.RoleRepository;
import com.gfg.springsecuritycustomauth.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;
import java.util.Set;
@Component
public class DataSeeder implements CommandLineRunner {
@Autowired
private UserRepository userRepository;
@Autowired
private RoleRepository roleRepository;
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@Override
public void run(String... args) throws Exception {
Role userRole = new Role();
userRole.setName("ROLE_USER");
roleRepository.save(userRole);
Role adminRole = new Role();
adminRole.setName("ROLE_ADMIN");
roleRepository.save(adminRole);
User user = new User();
user.setUsername("user");
user.setPassword(passwordEncoder.encode("userpassword"));
user.setRoles(Set.of(userRole));
userRepository.save(user);
User admin = new User();
admin.setUsername("admin");
admin.setPassword(passwordEncoder.encode("adminpassword"));
admin.setRoles(Set.of(adminRole));
userRepository.save(admin);
}
}
Step 11: Create the UserController Class
Java
package com.gfg.springsecuritycustomauth.controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api")
public class UserController {
// Public endpoint, accessible to anyone
@GetMapping("/public")
public String publicAccess() {
return "This is a public endpoint accessible to anyone.";
}
// Endpoint for authenticated users with ROLE_USER
@GetMapping("/user/dashboard")
@PreAuthorize("hasRole('USER')")
public String userDashboard() {
return "Welcome to the user dashboard!";
}
// Endpoint for authenticated users with ROLE_ADMIN
@GetMapping("/admin/dashboard")
@PreAuthorize("hasRole('ADMIN')")
public String adminDashboard() {
return "Welcome to the admin dashboard!";
}
}
Step 12: Main Class
Java
package com.gfg.springsecuritycustomauth;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringSecurityCustomAuthApplication {
public static void main(String[] args) {
SpringApplication.run(SpringSecurityCustomAuthApplication.class, args);
}
}
pom.xml
XML
<?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>3.3.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.gfg</groupId>
<artifactId>spring-security-custom-auth</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-security-custom-auth</name>
<description>spring-security-custom-auth</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>17</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-security</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</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>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
Step 13: Run the Application
After all the steps done, now run the project, and it will run on the port number 8080.
Step 14: Testing the Application
We will now test the application using the Postman tool.
1. Public Endpoint
http://localhost:8080/api/public
Output:
2. Admin Dashboard
http://localhost:8080/api/admin/dashboard
Output:
3. User Dashboard
GET http://localhost:8080/api/user/dashboard
Output:
Conclusion
In this article, we've explored how to customize authentication and authorization in a Spring Boot application using Spring Security. We've created a basic configuration that allows for role-based access control and defined user roles and permissions.