When I first set out to migrate my Spring Boot application from version 2 to 3, I was excited about all the new features and improvements. However, I soon hit a roadblock: my custom authentication logic built on older Spring Security libraries no longer worked. We don't see WebSecurityConfigurerAdapter in Spring Boot 3. Spring Security's APIs had changed significantly. This main reason a wirte this article.
Custom login pages allow us to provide a tailored authentication experience for users of our Spring Boot applications. The default login form provided by Spring Security is useful, but often we need more flexibility to match the rest of our UI.
We'll see how to:
- Create a login template with username and password fields
- Post the form to a custom controller endpoint
- Authenticate against user accounts in the database
- Configure Spring Security for custom authentication
The completed demo app includes these key features:
- Custom /login page for handling authentication
- Database-backed user accounts with encoded passwords
Let's dive in and see how to wire up these pieces!
Configuring Spring Security
Now we need to wire up Spring Security to use our custom authentication logic. This involves:
- Loading user details from the database
- Encoding passwords with BCrypt
- Registering our authentication provider
- We can accomplish the configuration in a
SpringSecurityConfig
:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authz -> authz
.requestMatchers("/login", "/error").permitAll()
.anyRequest().authenticated()
);
http.formLogin(authz -> authz
.loginPage("/login").permitAll()
);
http.logout(authz -> authz
.deleteCookies("JSESSIONID")
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(customUserDetailsService);
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
This ties everything together - let's look at the key pieces next.
The UserDetailsService
implementation can query this table to load credentials:
@Service
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User admin = userRepository.findByUsername(username).orElseThrow(() -> new UsernameNotFoundException("Cannot found user"));
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_" + admin.getRole()));
AuthUser authUser = new AuthUser(username, admin.getPassword(), authorities);
authUser.setId(admin.getId().toString());
authUser.setDisplayName(admin.getFirstName());
return authUser;
}
}
On startup, we also insert a test user with encoded password via BulkAdminUser.java
.
Conclusion
In this article we implemented a custom login page in Spring Boot 3 from start to finish. The key takeaways include:
- How to post login forms to your own controller
- Authenticating with a database-backed user store
- Configuring custom authentication in Spring Security
The full code for this demo is available on GitHub - check it out to see these concepts in action. You can also build on this foundation to craft targeted login experiences for your Spring applications.
Demo: https://github.com/jackynote/springboot3-example-login-spring-security
There's a lot more we could do, like adding remember-me, 2FA, and registration flows. But this covers the core and provides a great starting point for your custom auth needs. Let me know in the comments if you have any other questions!