Introduction
In Spring Security, the Principal
represents the currently authenticated user, while Authentication
contains the user’s authentication information, including their roles and permissions. Understanding how to access and utilize these details is crucial for implementing security in your Spring applications. This tutorial will guide you through the process of accessing Principal
and Authentication
details 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 Principal and Authentication Details
Create a controller to demonstrate accessing the Principal
and Authentication
details. 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;
import java.security.Principal;
@Controller
public class HomeController {
@GetMapping("/")
public String home(Model model, Principal principal) {
// Accessing Principal
model.addAttribute("username", principal.getName());
// Accessing Authentication
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
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 Principal and Authentication in Services
Sometimes you may need to access Principal
and Authentication
details 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;
import java.security.Principal;
@Controller
public class HomeController {
@Autowired
private UserService userService;
@GetMapping("/")
public String home(Model model, Principal principal) {
// Accessing Principal
model.addAttribute("username", principal.getName());
// Accessing Authentication
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
model.addAttribute("roles", authentication.getAuthorities());
// Using UserService
model.addAttribute("currentUsername", userService.getCurrentUsername());
model.addAttribute("isAdmin", userService.hasRole("ROLE_ADMIN"));
return "home";
}
@GetMapping("/login")
public String login() {
return "login";
}
}
Step 8: Add Some Test Users
Since the passwords in the CustomUserDetailsService
are hardcoded and hashed, you need to replace them with bcrypt hashes. You can generate bcrypt hashes using various online tools or libraries. For this example, let’s use these hashes:
- User password hash:
$2a$10$DowJonesIndex12345
- Admin password hash:
$2a$10$AdminPassword67890
Update your CustomUserDetailsService
with the correct bcrypt hashes:
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;
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if ("user".equals(username)) {
return User.withUsername("user")
.password("$2a$10$DowJonesIndex12345") // Replace with actual bcrypt hash
.authorities("ROLE_USER")
.build();
} else if ("admin".equals(username)) {
return User.withUsername("admin")
.password("$2a$10$AdminPassword67890") // Replace with actual bcrypt hash
.authorities("ROLE_ADMIN")
.build();
} else {
throw new UsernameNotFoundException("User not found");
}
}
}
Conclusion
In this tutorial, you have learned how to access Principal
and Authentication
details in Spring Security. You explored how to retrieve the currently authenticated user’s details, including their username and roles, both in controllers and service classes. This knowledge is essential for implementing authentication and authorization features in your Spring Boot applications, allowing you to manage user-specific functionality effectively.