Open In App

Spring Security – Customizing Authentication and Authorization

Last Updated : 23 Sep, 2024
Comments
Improve
Suggest changes
Like Article
Like
Report

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:

  1. Role-based authorization: Permissions are assigned based on roles.
  2. 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.

Project Metadata

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.

Add Dependencies

Project Structure

After project creation done, the folder structure will look like the below image:

Project Folder Structure

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.

Application Runs
Console log

Step 14: Testing the Application

We will now test the application using the Postman tool.

1. Public Endpoint

http://localhost:8080/api/public

Output:

postman ui


2. Admin Dashboard

http://localhost:8080/api/admin/dashboard

Output:

Admin Dashboard


3. User Dashboard

GET http://localhost:8080/api/user/dashboard

Output:

User Dashboard

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.


Next Article

Similar Reads