Spring Security UserDetailsService Using Spring Boot 3 java Security Spring Spring Boot Spring Boot 3 Spring Security by devs5003 - January 13, 2024March 15, 20243 Last Updated on March 15th, 2024In continuation to series of articles on Spring Security, here in this article we will learn ‘How to implement Security in Spring Boot using UserDetailsService With Spring Boot 3?’. After going through the previous articles, I hope we all are very familiar with basics of Security and even the basics of Security in a Spring Boot application. After the release of Spring Boot 3, here we are going to implement ‘Spring Security UserDetailsService Using Spring Boot 3’. In this article, we will create a user registration form and save users with their roles in the database. Then, based on the user role, we will check the authentication and authorization functionalities with the help of predefined UserDetailsService. To illustrate, we will take some roles into effect and play around them in the whole process to make it crystal clear. Additionally, we will have some pages and restrict them to be accessible by some specific roles only. In order to make it possible, we will create a small, simple MVC web application and make registration process open for all users. Users will select their respective roles while filling registration form. Then, we will implement security features on top of it. Let’s start working on our topic ‘Spring Security UserDetailsService Using Spring Boot 3’ accordingly. Table of Contents Toggle What can you expect from this article as a whole?Software/Technologies Used in the ExampleJars UsedWhat is UserDetailsService all about? What is the benefit of using it?How to incorporate UserDetailsService Security in our application?How can we implement Role Based Security in a Spring Based Application?Example of Spring Security UserDetailsService Using Spring Boot 3Use case DetailsStep#1: Create a Spring Boot Starter Project in STS(Spring Tool Suite)Step#2: Update database properties in application.properties fileStep#3: Create User Entity & Repository classesStep#4: Create AppConfig class to instantiate BCryptPasswordEncoderStep#5: Create Service Interface & Service Implementation classStep#6: Create a UserController classStep#7: Write a Controller class to navigate through pagesStep#8: Write UI pages(Thymeleaf) Step#9: Write SecurityConfig class Without Using WebSecurityConfigurerAdapterHow to test the security enabled Application ?How To Migrate your previous implementation to Spring Security UserDetailsService Using Spring Boot 3?TroubleshootingSummary What can you expect from this article as a whole? 1) What is a UserDetailsService concept in the context of Spring Security? 2) What is the benefits of implementing UserDetailsService? 3) How to implement Spring Security UserDetailsService Using Spring Boot 3? 4) How to Implement Role Based Security in a Spring Based application? 5) Moreover, how and where to use annotations :@EnableWebSecurity, @Configuration, @Bean, @GetMapping, @Autowired, @Data, @Entity, @Table, @Id, @GeneratedValue, @Column, @ElementCollection, @CollectionTable, @JoinColumn, @Service 6) How to develop a user registration app using Spring MVC with Thymeleaf? 7) How to test the Security enabled application? 8) How to implement Security in Spring Boot using UserDetailsService Without WebSecurityConfigurerAdapter? Software/Technologies Used in the Example Sometimes some version conflicts with other version. Hence, listing down the combinations that are tested to be working with each other. Below is the tested combination of software that are used to develop Spring Security UserDetailsService Using Spring Boot 3. It also makes the implementation flawless. 1) Spring Boot 3.0.0 2) JDK 17 or later 3) Maven 3.8.1 4) IDE – STS 4.7.1. RELEASE Jars Used Below are the list of major jars automatically downloaded by maven using pom.xml in these examples. They might be useful to cross verify in case if you face any issue in execution. 1) spring-boot-3.0.0.jar 2) spring-boot-starter-3.0.0.jar 3) spring-boot-starter-security-3.0.0.jar 4) spring-core-6.0.2.jar 5) spring-security-core-6.0.0.jar 6) thymeleaf-spring6-3.1.0.RELEASE.jar What is UserDetailsService all about? What is the benefit of using it? UserDetailsService is a predefined interface provided by Spring framework under the package org.springframework.security.core.userdetails. In order to make use of UserDetailsService, our implementation class implements this interface and overrides it’s loadUserByUsername(String username) method. The return type of this method is UserDetails which is again an interface. Predefined User class (org.springframework.security.core.userdetails.User) is an implementation of UserDetails interface. Furthermore, we pass our username in loadUserByUsername(String username) method, and it returns us predefined User object(org.springframework.security.core.userdetails.User). In fact, we provide only username to UserDetailsService and some small configurations. As a result, we get all role based in-built security functionality implemented as part of the framework. Accordingly, we save a lot of effort in implementing security when we use UserDetailsService interface. How to incorporate UserDetailsService Security in our application? First of all, you must have a Spring Boot web application where in you will have a form i.e. a kind of User registration form. As part of the Spring MVC structure you will have a UserService implementation class. Let’s say it UserServiceImpl.java. The second thing to remember is that you have to convert your User object into predefined Spring’s User object. Further, please follow below steps to get UserDetailsService implemented in your application. 1) Your user service class ‘UserServiceImpl.java’ should implement interface UserDetailsService.java(Provided by Spring) 2) Equally important, Override loadUserByUsername(String username) method of interface UserDetailsService in your UserServiceImpl class. 3) As part of implementation, (A) Get your User Object with the help of username/email from UserRepository. (B) Convert your User Object into Spring’s predefined User object(org.springframework.security.core.userdetails.User) accordingly. (C) Return Spring defined User object which is an implementation of UserDetails(method’s return type). Below code represents the implementation of UserDetailsService. However, you will see the complete code in below sections consequently. UserServiceImpl.java import java.util.Optional;import java.util.stream.Collectors;import org.springframework.beans.factory.annotation.Autowired;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.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.stereotype.Service;import com.dev.springboot.security.UserDetailsService.model.User;import com.dev.springboot.security.UserDetailsService.repo.UserRepository;import com.dev.springboot.security.UserDetailsService.service.IUserService;@Servicepublic class UserServiceImpl implements IUserService, UserDetailsService{ @Autowired private UserRepository userRepo; @Autowired private BCryptPasswordEncoder passwordEncoder; @Override public Integer saveUser(User user) { String passwd= user.getPassword(); String encodedPasswod = passwordEncoder.encode(passwd); user.setPassword(encodedPasswod); user = userRepo.save(user); return user.getId(); } @Override public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { Optional<User> opt = userRepo.findUserByEmail(email); if(opt.isEmpty()) throw new UsernameNotFoundException("User with email: " +email +" not found !"); else { User user = opt.get(); return new org.springframework.security.core.userdetails.User( user.getEmail(), user.getPassword(), user.getRoles() .stream() .map(role-> new SimpleGrantedAuthority(role)) .collect(Collectors.toSet()) ); } } //Other Approach: Without Using Lambda & Stream API Of Java 8 /** @Override public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { Optional<User> opt = userRepo.findUserByEmail(email); org.springframework.security.core.userdetails.User springUser=null; if(opt.isEmpty()) { throw new UsernameNotFoundException("User with email: " +email +" not found"); } User user =opt.get(); List<String> roles = user.getRoles(); Set<GrantedAuthority> ga = new HashSet<>(); for(String role:roles) { ga.add(new SimpleGrantedAuthority(role)); } springUser = new org.springframework.security.core.userdetails.User( email, user.getPassword(), ga ); return springUser; } */ } How can we implement Role Based Security in a Spring Based Application? Typically, in a Spring Based Application, we implement Role Based Access by creating a java class and applying @EnableWebSecurity and @Configuration on top of it. @EnableWebSecurity enables Spring Security features in the application, whereas @Configuration represents that this class is a configuration class. For example, below code demonstrates the implementation of a role based security. SecurityConfig.java package com.dev.springboot.security.UserDetailsService.config;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.security.authentication.AuthenticationProvider;import org.springframework.security.authentication.dao.DaoAuthenticationProvider;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.security.web.SecurityFilterChain;import org.springframework.security.web.util.matcher.AntPathRequestMatcher;@EnableWebSecurity@Configurationpublic class SecurityConfig { @Autowired private UserDetailsService uds; @Autowired private BCryptPasswordEncoder encoder; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/home","/register","/saveUser").permitAll() .antMatchers("/welcome").authenticated() .antMatchers("/admin").hasAuthority("Admin") .antMatchers("/mgr").hasAuthority("Manager") .antMatchers("/emp").hasAuthority("Employee") .antMatchers("/hr").hasAuthority("HR") .antMatchers("/common").hasAnyAuthority("Employeee,Manager,Admin") .anyRequest().authenticated() .and() .formLogin() .defaultSuccessUrl("/welcome",true) .and() .logout() .logoutRequestMatcher(new AntPathRequestMatcher("/logout")) .and() .exceptionHandling() .accessDeniedPage("/accessDenied") .and() .authenticationProvider(authenticationProvider()); return http.build(); } @Bean public AuthenticationProvider authenticationProvider() { DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider(); authenticationProvider.setUserDetailsService(uds); authenticationProvider.setPasswordEncoder(encoder); return authenticationProvider; }} Example of Spring Security UserDetailsService Using Spring Boot 3 In order to simplify the implementation of Spring Security UserDetailsService Using Spring Boot 3, let’s consider a use case. Use case Details Let’s assume an internal portal of a small organization. In the organization, we have various roles within employees such as Admin, HR, Manager and of course Employee as well. Additionally, the Portal has role based access to pages. Furthermore, some pages should be accessible to all roles such as registration and the public info pages, while others should be restricted to their respective roles only. The organization will have a user registration page which must be accessible to all users, even without login. Now let’s create a standard user registration flow as given below. Step#1: Create a Spring Boot Starter Project in STS(Spring Tool Suite) While creating Starter Project select ‘Spring Security’, ‘Thymeleaf’, ‘Spring Web’, ‘Spring Data JPA’, ‘MySQL Driver’, ‘Lombok’ and ‘Spring Boot DevTools’ as starter project dependencies. Even If you don’t know how to create a Spring Boot Starter Project, Kindly visit internal link on ‘How to create a starter project in Spring boot?‘. Also, if you want to know more about Lombok, then visit Internal Link on Lombok. Step#2: Update database properties in application.properties file Update application.properties to connect to MySQL Database. Please note that we can also omit driver-class-name as Spring Boot will automatically find it from the database URL as shown below. However, it is recommended to keep it. #application.properties --------------------------------------------------------------------- #-------------------- server properties --------------- server.port=8080 #--------------------- DB Connection ------------------ #AutoLoading of driver class since JDBC 4 #spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/testBootSecurity spring.datasource.username=root spring.datasource.password=devs #--------------------JPA-ORM Properties----------------- spring.jpa.show-sql=true spring.jpa.hibernate.ddl-auto=update #spring.jpa.database-platform=org.hibernet.dialect.MySQL8Dialect Step#3: Create User Entity & Repository classes Now create User.java & UserRepositoty.java as below. Please note that Spring Boot 3.0.0 and Spring Security 6.0 onward, all the import statements starting with ‘javax’ are replaced with ‘jakarta’ as shown in the code below. For example: ‘javax.persistence.Entity;’ should be replaced with ‘jakarta.persistence.Entity;’. Equally important, User.java has a variable ‘roles’ of type List<String>. It will create a separate table in the database with two columns user_id and user_role consequently. Further, @ElementCollection(fetch= FetchType.EAGER) indicates that while fetching User object, also fetch roles simultaneously. On the other hand, UserRepository extends ‘JpaRepository’ to take advantage of inbuilt database operations. User.java package com.dev.springboot.security.UserDetailsService.model;import java.util.List;import jakarta.persistence.CollectionTable;import jakarta.persistence.Column;import jakarta.persistence.ElementCollection;import jakarta.persistence.Entity;import jakarta.persistence.FetchType;import jakarta.persistence.GeneratedValue;import jakarta.persistence.Id;import jakarta.persistence.JoinColumn;import jakarta.persistence.Table;import lombok.Data;@Data@Entity@Table(name="users")public class User { @Id @GeneratedValue @Column(name="user_id") private Integer id; @Column(name="user_name") private String name; @Column(name="user_passwd") private String password; @Column(name="user_email") private String email; @ElementCollection(fetch= FetchType.EAGER) @CollectionTable( name="roles", joinColumns = @JoinColumn(name="user_id") ) @Column(name="user_role") private List<String> roles; } UserRepository.java package com.dev.springboot.security.UserDetailsService.repo;import java.util.Optional;import org.springframework.data.jpa.repository.JpaRepository;import com.dev.springboot.security.UserDetailsService.model.User;public interface UserRepository extends JpaRepository<User, Integer> { Optional<User> findUserByEmail(String email);} Step#4: Create AppConfig class to instantiate BCryptPasswordEncoder As BCryptPasswordEncoder is a predefined class, hence we need to provide it’s instantiation code in AppConfig.java as a configuration class. Further, BCryptPasswordEncoder will be required to encode our password values in other classes. AppConfig.java package com.dev.springboot.security.UserDetailsService.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;@Configurationpublic class AppConfig { @Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }} Step#5: Create Service Interface & Service Implementation class Create Service Interface and Service Impl class as IUserService.java and UserServiceImpl.java accordingly as shown below. In fact, implementation of loadUserByUsername(String email) method in UserServiceImpl.java is the most important part of your UserDetailsService on the whole. IUserService.java package com.dev.springboot.security.UserDetailsService.service;import com.dev.springboot.security.UserDetailsService.model.User;public interface IUserService { public Integer saveUser(User user);} UserServiceImpl.java package com.dev.springboot.security.UserDetailsService.service.impl;import java.util.Optional;import java.util.stream.Collectors;import org.springframework.beans.factory.annotation.Autowired;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.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.stereotype.Service;import com.dev.springboot.security.UserDetailsService.model.User;import com.dev.springboot.security.UserDetailsService.repo.UserRepository;import com.dev.springboot.security.UserDetailsService.service.IUserService;@Servicepublic class UserServiceImpl implements IUserService, UserDetailsService{ @Autowired private UserRepository userRepo; @Autowired private BCryptPasswordEncoder passwordEncoder; @Override public Integer saveUser(User user) { String passwd= user.getPassword(); String encodedPasswod = passwordEncoder.encode(passwd); user.setPassword(encodedPasswod); user = userRepo.save(user); return user.getId(); } @Override public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { Optional<User> opt = userRepo.findUserByEmail(email); if(opt.isEmpty()) throw new UsernameNotFoundException("User with email: " +email +" not found !"); else { User user = opt.get(); return new org.springframework.security.core.userdetails.User( user.getEmail(), user.getPassword(), user.getRoles() .stream() .map(role-> new SimpleGrantedAuthority(role)) .collect(Collectors.toSet()) ); } } //Other Approach: Without Using Lambda & Stream API Of Java 8 /** @Override public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { Optional<User> opt = userRepo.findUserByEmail(email); org.springframework.security.core.userdetails.User springUser=null; if(opt.isEmpty()) { throw new UsernameNotFoundException("User with email: " +email +" not found"); } User user =opt.get(); List<String> roles = user.getRoles(); Set<GrantedAuthority> ga = new HashSet<>(); for(String role:roles) { ga.add(new SimpleGrantedAuthority(role)); } springUser = new org.springframework.security.core.userdetails.User( email, user.getPassword(), ga ); return springUser; } */ } Step#6: Create a UserController class Subsequently, write a controller class for User as ‘UserController.java’ which will control the user registration page. UserController.java package com.dev.springboot.security.UserDetailsService.controller;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.ui.Model;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.ModelAttribute;import org.springframework.web.bind.annotation.PostMapping;import com.dev.springboot.security.UserDetailsService.model.User;import com.dev.springboot.security.UserDetailsService.service.IUserService;@Controllerpublic class UserController { @Autowired private IUserService userService; // Go to Registration Page @GetMapping("/register") public String register() { return "registerUser"; } // Read Form data to save into DB @PostMapping("/saveUser") public String saveUser( @ModelAttribute User user, Model model ) { Integer id = userService.saveUser(user); String message = "User '"+id+"' saved successfully !"; model.addAttribute("msg", message); return "registerUser"; }} Step#7: Write a Controller class to navigate through pages In spite of UserController, write one more controller class and call it as ‘HomeController.java’. This class will be responsible to navigate through different pages. HomeController.java package com.dev.springboot.security.UserDetailsService.controller;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.GetMapping;@Controllerpublic class HomeController { @GetMapping("/home") public String getHomePage() { return "homePage"; } @GetMapping("/welcome") public String getWelcomePage() { return "welcomePage"; } @GetMapping("/admin") public String getAdminPage() { return "adminPage"; } @GetMapping("/emp") public String getEmployeePage() { return "empPage"; } @GetMapping("/mgr") public String getManagerPage() { return "mgrPage"; } @GetMapping("/hr") public String getHrPage() { return "hrPage"; } @GetMapping("/common") public String getCommonPage() { return "commonPage"; } @GetMapping("/accessDenied") public String getAccessDeniedPage() { return "accessDeniedPage"; }} Step#8: Write UI pages(Thymeleaf) Below are the .html files for UI pages. Place these pages inside ‘src/main/resources/templates’ folder accordingly. registerUser.html <!DOCTYPE html><html xmlns:th="http://www.thymeleaf.org"><head><meta charset="ISO-8859-1"><title>User Registration</title></head><body><h3>User Registration</h3><form action="saveUser" method="post"><pre>Name : <input type="text" name="name"/>Email: <input type="text" name="email"/>Password: <input type="password" name="password"/>Role(s): <input type="checkbox" name="roles" value="Admin"/>Admin <input type="checkbox" name="roles" value="Manager"/>Manager <input type="checkbox" name="roles" value="HR"/>HR <input type="checkbox" name="roles" value="Employee"/>Employee<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/> <input type="submit" value="Register"/></pre></form><div th:text="${msg}"></div></body></html> homePage.html <!DOCTYPE html><html xmlns:th="http://www.thymeleaf.org"><head><meta charset="ISO-8859-1"><title>Insert title here</title></head><body><h3> welcome to the Home Page </h3>This page is accessible to ALL.</body></html> welcomePage.html <!DOCTYPE html><html xmlns:th="http://www.thymeleaf.org"><head><meta charset="ISO-8859-1"><title>Insert title here</title></head><body><h3> Welcome Page after successful Login</h3><a th:href="@{/logout}" >LOGOUT</a></body></html> adminPage.html <!DOCTYPE html><html xmlns:th="http://www.thymeleaf.org"><head><meta charset="ISO-8859-1"><title>Insert title here</title></head><body><h3> Admin Page </h3>Welcome to Admin page.!!!<a th:href="@{/logout}" >LOGOUT</a></body></html> empPage.html <!DOCTYPE html><html xmlns:th="http://www.thymeleaf.org"><head><meta charset="ISO-8859-1"><title>Insert title here</title></head><body><h3> Employee Page </h3><a th:href="@{/logout}" >LOGOUT</a></body></html> mgrPage.html <!DOCTYPE html><html xmlns:th="http://www.thymeleaf.org"><head><meta charset="ISO-8859-1"><title>Insert title here</title></head><body><h3> Manager Page </h3><a th:href="@{/logout}" >LOGOUT</a></body></html> hrPage.html <!DOCTYPE html><html xmlns:th="http://www.thymeleaf.org"><head><meta charset="ISO-8859-1"><title>Insert title here</title></head><body><h3> HR Page </h3><a th:href="@{/logout}" >LOGOUT</a></body></html> commonPage.html <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="ISO-8859-1"> <title>Insert title here</title> </head> <body> <h3>You are not allowed to access this page. Please go to Welcome Page</h3> <a th:href="@{/welcome}" >Welcome</a> <a th:href="@{/logout}" >LOGOUT</a> </body> </html> accessDeniedPage.html <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="ISO-8859-1"> <title>Insert title here</title> </head> <body> <h3>You are not allowed to access this page. Please go to Welcome Page</h3> <a th:href="@{/welcome}" >Welcome</a> <a th:href="@{/logout}" >LOGOUT</a> </body> </html> Step#9: Write SecurityConfig class Without Using WebSecurityConfigurerAdapter At the end, write the other important class as SecurityConfig.java. Prior to Spring Security 5.7.0-M2, this class was supposed to extend a predefined class WebSecurityConfigurerAdapter.java and implement two configure() methods accordingly. But since Spring Security 5.7.0-M2, the WebSecurityConfigurerAdapter has been deprecated. Furthermore, since Spring Boot 3.0.0 and Spring Security 6.0, the WebSecurityConfigurerAdapter is completely removed from the Spring Security API. Hence, the required implementation is applicable since Spring 3.0.0 as shown in below code snippet. SecurityConfig.java package com.dev.springboot.security.UserDetailsService.config;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.security.authentication.AuthenticationProvider;import org.springframework.security.authentication.dao.DaoAuthenticationProvider;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.security.web.SecurityFilterChain;import org.springframework.security.web.util.matcher.AntPathRequestMatcher;@EnableWebSecurity@Configurationpublic class SecurityConfig { @Autowired private UserDetailsService uds; @Autowired private BCryptPasswordEncoder encoder; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests() .requestMatchers("/home","/register","/saveUser").permitAll() .requestMatchers("/welcome").authenticated() .requestMatchers("/admin").hasAuthority("Admin") .requestMatchers("/mgr").hasAuthority("Manager") .requestMatchers("/emp").hasAuthority("Employee") .requestMatchers("/hr").hasAuthority("HR") .requestMatchers("/common").hasAnyAuthority("Employeee", "Manager", "Admin") .anyRequest().authenticated() .and() .formLogin() .defaultSuccessUrl("/welcome",true) .and() .logout() .logoutRequestMatcher(new AntPathRequestMatcher("/logout")) .and() .exceptionHandling() .accessDeniedPage("/accessDenied") .and() .authenticationProvider(authenticationProvider()); return http.build(); } @Bean public AuthenticationProvider authenticationProvider() { DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider(); authenticationProvider.setUserDetailsService(uds); authenticationProvider.setPasswordEncoder(encoder); return authenticationProvider; }} From the above implementation of SecurityConfig, it is clear that some of the methods from older versions are also removed. For example: authorizeRequests() -> authorizeHttpRequests() antMatchers() -> requestMatchers()   regexMatchers() -> RegexRequestMatchers() Finally, we have completed the coding part. How to test the security enabled Application ? Although the word ‘testing’ looks very easy to a developer, but it is equally important as it provides the result of our implementation on the whole. While testing the application you should keep configure(HttpSecurity http) method of SecurityConfig class in front of you, then follow the steps given below: 1) Start the application : Right click on the project, then select “Run As’ >> ‘Spring Boot App’. 2) Enter the registration page URL http://localhost:8080/register, then check if it is accessible to everyone without even login to the application. 3) Enter the required field values and complete the registration process by clicking on the ‘Register’ button accordingly. 4) Now Enter any URL specific to the role you selected in registration. Suppose you entered URL http://localhost:8080/admin, then it should redirect you to the in-built Login page. 5) Enter the credentials(email id in place of username) and Login to the application. It will redirect you to the default success URL which is welcome page. 6) Now enter the URL http://localhost:8080/admin again, this time you will be able to access the admin page. 7) Repeat the above steps for other roles as well. Additionally, as mentioned above keep the SecurityConfig.java code in front of you and test each scenario subsequently. How To Migrate your previous implementation to Spring Security UserDetailsService Using Spring Boot 3? Here are some step by step guidelines, that you can follow to migrate from older version implementation to Spring Security UserDetailsService Using Spring Boot 3. 1) As recommended by Spring Official documentation, first upgrade your implementation to Spring Boot 2.7.0 if it is implemented using lower versions. We can do it by updating Spring Boot version in pom.xml as below. <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.0</version> <relativePath/> <!-- lookup parent from repository --> </parent> Once updated, save the pom.xml and let the maven download the new dependencies. 2) For this example, you will find the WebSecurityConfigurerAdapter as deprecated. Provide the new implementation without using WebSecurityConfigurerAdapter. You may use the implementation of SecurityConfig.java from this article. If there are some other errors, fix them as well. 3) In the next step, configure JDK 17 environment in your project as recommended in Spring Boot Official documentation. 4) Update the Spring Boot version as ‘3.0.0’ in pom.xml, save the file and let the maven download the new dependencies. 5) Fix the compilation errors as shown below: (A) In SecurityConfig.java: Replace authorizeRequests() with authorizeHttpRequests() Replace antMatchers() with requestMatchers() Replace regexMatchers() with RegexRequestMatchers() (B) In Entity class: In import statements, replace all occurrences of ‘javax’ with ‘jakarta’. For example: ‘javax.persistence.Entity;’ should be replaced with ‘jakarta.persistence.Entity;’. Troubleshooting While upgrading Spring Boot version to 3.0.0, you may face below error: [ERROR] Some problems were encountered while processing the POMs: [ERROR] 'dependencies.dependency.version' for org.thymeleaf.extras:thymeleaf-extras-springsecurity5:jar is missing. In order to resolve this error, update the version of Thymeleaf as shown below. <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity5</artifactId> <version>3.0.4.RELEASE</version> </dependency> Once the update is done, maven will automatically download the competent dependencies. Summary After going through all the theoretical & examples part of ‘Spring Security UserDetailsService Using Spring Boot 3’, finally, we should be able to implement role based web security in a Spring Boot project. Of course, In this article we have covered the third way of implementing security feature. Similarly, we will talk about some more ways of security when it comes into the picture in our upcoming articles. If there is any change in the future, we will update the same accordingly. If you want to know What’s new In Spring Boot 3.0, kindly visit our separate article on ‘New Features In Spring Boot 3‘. Moreover, Feel free to provide your comments in the comments section. Related
Great post! I’m glad to see a detailed explanation of how to implement UserDetailsService in Spring Security using Spring Boot 3. It’s always helpful to see examples and code snippets to help me understand how to implement security features in my own projects. Thanks for sharing your knowledge! Reply