You are currently viewing Understanding the Security Context in Spring Security: A Tutorial

Understanding the Security Context in Spring Security: A Tutorial

Introduction

The Security Context in Spring Security is a fundamental concept that holds the security information of the currently authenticated user. It encapsulates details about the principal (the user), the granted authorities (roles and permissions), and other security-related information. This tutorial will guide you through understanding and working with the Security Context in Spring Security, demonstrating how to access and manipulate it in a Spring Boot application.

Prerequisites

Before starting, ensure you have the following installed:

  • JDK 8 or later
  • Maven or Gradle
  • An IDE like IntelliJ IDEA or Eclipse
  • Basic knowledge of Spring Boot and Spring Security

Step 1: Set Up Your Spring Boot Project

First, create a new Spring Boot project. You can use Spring Initializr (https://start.spring.io/) to generate the project. Select the following dependencies:

  • Spring Web
  • Spring Security

Download the project and import it into your IDE.

Step 2: Basic Spring Security Configuration

Start by creating a basic Spring Security configuration. Create a configuration class in src/main/java/com/example/demo/config/SecurityConfig.java:

package com.example.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/public/**").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()
                .permitAll();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

Step 3: Create a UserDetailsService

To manage user authentication, create a custom UserDetailsService. This service will load user-specific data. Create CustomUserDetailsService in src/main/java/com/example/demo/service/CustomUserDetailsService.java:

package com.example.demo.service;

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 java.util.Collections;

@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // For demonstration purposes, we are using hardcoded users.
        // In a real application, you would fetch user details from a database.
        if ("user".equals(username)) {
            return User.withUsername("user")
                    .password("$2a$10$DowJonesIndex12345")
                    .authorities("ROLE_USER")
                    .build();
        } else if ("admin".equals(username)) {
            return User.withUsername("admin")
                    .password("$2a$10$AdminPassword67890")
                    .authorities("ROLE_ADMIN")
                    .build();
        } else {
            throw new UsernameNotFoundException("User not found");
        }
    }
}

Update the security configuration to use this UserDetailsService:

package com.example.demo.config;

import com.example.demo.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.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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomUserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/public/**").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()
                .permitAll();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

Step 4: Create Controllers to Access Security Context

Create a controller to demonstrate accessing the Security Context. Create HomeController in src/main/java/com/example/demo/controller/HomeController.java:

package com.example.demo.controller;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {

    @GetMapping("/")
    public String home(Model model) {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        String currentPrincipalName = authentication.getName();
        model.addAttribute("username", currentPrincipalName);

        Object principal = authentication.getPrincipal();
        if (principal instanceof UserDetails) {
            String username = ((UserDetails) principal).getUsername();
            model.addAttribute("roles", ((UserDetails) principal).getAuthorities());
        } else {
            model.addAttribute("roles", authentication.getAuthorities());
        }

        return "home";
    }

    @GetMapping("/login")
    public String login() {
        return "login";
    }
}

Step 5: Create Views

Create simple Thymeleaf templates for the views in src/main/resources/templates/.

home.html:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Home</title>
</head>
<body>
    <h1>Home Page</h1>
    <p>Welcome, <span th:text="${username}">User</span>!</p>
    <p>Your roles are: <span th:each="role : ${roles}" th:text="${role.getAuthority()}"></span></p>
    <a href="/logout">Logout</a>
</body>
</html>

login.html:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Login</title>
</head>
<body>
    <h1>Login</h1>
    <form th:action="@{/login}" method="post">
        <div>
            <label for="username">Username:</label>
            <input type="text" id="username" name="username"/>
        </div>
        <div>
            <label for="password">Password:</label>
            <input type="password" id="password" name="password"/>
        </div>
        <div>
            <button type="submit">Login</button>
        </div>
    </form>
</body>
</html>

Step 6: Accessing Security Context in Services

Sometimes you may need to access the security context within service classes. Here’s an example of how you can do that.

Create a service class in src/main/java/com/example/demo/service/UserService.java:

package com.example.demo.service;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    public String getCurrentUsername() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication != null && authentication.isAuthenticated()) {
            Object principal = authentication.getPrincipal();
            if (principal instanceof UserDetails) {
                return ((UserDetails) principal).getUsername();
            }
        }
        return null;
    }

    public boolean hasRole(String role) {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication != null && authentication.isAuthenticated()) {
            return authentication.getAuthorities().stream()
                    .anyMatch(grantedAuthority -> grantedAuthority.getAuthority().equals(role));
        }
        return false;
    }
}

Step 7: Using UserService in Controllers

You can now use the UserService in your controllers to get information about the currently authenticated user.

Update HomeController to use UserService:

package com.example.demo.controller;

import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {

    @Autowired
    private UserService userService;

    @GetMapping("/")
    public String home(Model model) {
        model.addAttribute("username", userService.getCurrentUsername());
        model.addAttribute("roles", SecurityContextHolder.getContext().getAuthentication().getAuthorities());
        return "home";
    }

    @GetMapping("/login")
    public String login() {
        return "login";
    }
}

Conclusion

In this tutorial, you have learned how to work with the Security Context in Spring Security. You explored how to access the currently authenticated user and

their roles, both in controllers and service classes. Understanding and effectively using the Security Context is crucial for managing authentication and authorization in your Spring Security applications. This knowledge allows you to secure your application more robustly and provide a better user experience.

Leave a Reply