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.