If you're working on a Spring Security (and especially an OAuth) implementation, definitely have a look at the Learn Spring Security course:
>> LEARN SPRING SECURITYMocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.
Get started with mocking and improve your application tests using our Mockito guide:
Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.
Get started with understanding multi-threaded applications with our Java Concurrency guide:
Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:
Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.
But these can also be overused and fall into some common pitfalls.
To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:
Get started with Spring and Spring Boot, through the Learn Spring course:
>> LEARN SPRINGExplore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:
Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.
I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.
You can explore the course here:
Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.
Get started with Spring Data JPA through the guided reference course:
Refactor Java code safely β and automatically β with OpenRewrite.
Refactoring big codebases by hand is slow, risky, and easy to put off. Thatβs where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.
Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions β one for newcomers and one for experienced users. Youβll see how recipes work, how to apply them across projects, and how to modernize code with confidence.
Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.
1. Overview
Spring Security allows customizing HTTP security for features, such as endpoints authorization or the authentication manager configuration, by extending a WebSecurityConfigurerAdapter class. However, in recent versions, Spring deprecates this approach and encourages a component-based security configuration.
In this tutorial, weβll learn how we can replace this deprecation in a Spring Boot application and run some MVC tests.
Further reading:
Warning: "The type WebMvcConfigurerAdapter is deprecated"
Default Password Encoder in Spring Security
2. Spring Security Without the WebSecurityConfigurerAdapter
We commonly see Spring HTTP security configuration classes that extend a WebSecurityConfigureAdapter class.
However, beginning with version 5.7.0-M2, Spring deprecates the use of WebSecurityConfigureAdapter and suggests creating configurations without it.
Letβs create an example Spring Boot application using in-memory authentication to show this new type of configuration.
First, weβll define our configuration class:
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig {
// config
}
Weβll add method security annotations to enable processing based on different roles.
2.1. Configure Authentication
With the WebSecurityConfigureAdapter, weβll use an AuthenticationManagerBuilder to set our authentication context.
Now, if we want to avoid deprecation, we can define a UserDetailsManager or UserDetailsService component:
@Bean
public UserDetailsService userDetailsService(BCryptPasswordEncoder bCryptPasswordEncoder) {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("user")
.password(bCryptPasswordEncoder.encode("userPass"))
.roles("USER")
.build());
manager.createUser(User.withUsername("admin")
.password(bCryptPasswordEncoder.encode("adminPass"))
.roles("USER", "ADMIN")
.build());
return manager;
}
Or, given our UserDetailService, we can even set an AuthenticationManager:
@Bean
public AuthenticationManager authenticationManager(HttpSecurity http, BCryptPasswordEncoder bCryptPasswordEncoder, UserDetailService userDetailService)
throws Exception {
return http.getSharedObject(AuthenticationManagerBuilder.class)
.userDetailsService(userDetailsService)
.passwordEncoder(bCryptPasswordEncoder)
.and()
.build();
}
Similarly, this will work if we use JDBC or LDAP authentication.
2.2. Configure HTTP Security
More importantly, if we want to avoid deprecation for HTTP security, we can create a SecurityFilterChain bean.
For example, suppose we want to secure the endpoints depending on the roles, and leave an anonymous entry point only for login. Weβll also restrict any delete request to an admin role. Weβll use Basic Authentication:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(authorizationManagerRequestMatcherRegistry ->
authorizationManagerRequestMatcherRegistry.requestMatchers(HttpMethod.DELETE).hasRole("ADMIN")
.requestMatchers("/admin/**").hasAnyRole("ADMIN")
.requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")
.requestMatchers("/login/**").permitAll()
.anyRequest().authenticated())
.httpBasic(Customizer.withDefaults())
.sessionManagement(httpSecuritySessionManagementConfigurer -> httpSecuritySessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return http.build();
}
The HTTP security will build a DefaultSecurityFilterChain object to load request matchers and filters.
2.3. Configure Web Security
For Web security, we can now use the callback interface WebSecurityCustomizer.
Weβll add a debug level and ignore some paths, like images or scripts:
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return web -> web.debug(securityDebug).ignoring().requestMatchers("/css/**", "/js/**", "/img/**", "/lib/**", "/favicon.ico");
}
3. Endpoints Controller
Now weβll define a simple REST controller class for our application:
@RestController
public class ResourceController {
@GetMapping("/login")
public String loginEndpoint() {
return "Login!";
}
@GetMapping("/admin")
public String adminEndpoint() {
return "Admin!";
}
@GetMapping("/user")
public String userEndpoint() {
return "User!";
}
@GetMapping("/all")
public String allRolesEndpoint() {
return "All Roles!";
}
@DeleteMapping("/delete")
public String deleteEndpoint(@RequestBody String s) {
return "I am deleting " + s;
}
}
As we mentioned earlier when defining HTTP security, weβll add a generic /login endpoint accessible by anyone, specific endpoints for admin and user, and an /all endpoint not secured by a role, but still requiring authentication.
4. Test Endpoints
Letβs add our new configuration to a Spring Boot Test using an MVC mock to test our endpoints.
4.1. Test Anonymous Users
Anonymous users can access the /login endpoint. If they try to access something else, theyβll be unauthorized (401):
@Test
@WithAnonymousUser
public void whenAnonymousAccessLogin_thenOk() throws Exception {
mvc.perform(get("/login"))
.andExpect(status().isOk());
}
@Test
@WithAnonymousUser
public void whenAnonymousAccessRestrictedEndpoint_thenIsUnauthorized() throws Exception {
mvc.perform(get("/all"))
.andExpect(status().isUnauthorized());
}
Moreover, for all the endpoints except /login, we always require authentication, like for the /all endpoint.
4.2. Test User Role
A user role can access generic endpoints and all the other paths we granted for this role:
@Test
@WithUserDetails()
public void whenUserAccessUserSecuredEndpoint_thenOk() throws Exception {
mvc.perform(get("/user"))
.andExpect(status().isOk());
}
@Test
@WithUserDetails()
public void whenUserAccessRestrictedEndpoint_thenOk() throws Exception {
mvc.perform(get("/all"))
.andExpect(status().isOk());
}
@Test
@WithUserDetails()
public void whenUserAccessAdminSecuredEndpoint_thenIsForbidden() throws Exception {
mvc.perform(get("/admin"))
.andExpect(status().isForbidden());
}
@Test
@WithUserDetails()
public void whenUserAccessDeleteSecuredEndpoint_thenIsForbidden() throws Exception {
mvc.perform(delete("/delete"))
.andExpect(status().isForbidden());
}
Itβs worth noticing that if a user role tries to access an admin-secured endpoint, the user gets a βforbiddenβ (403) error.
Conversely, someone with no credentials, like an anonymous in the previous example, will get an βunauthorizedβ error (401).
4.3. Test Admin Role
As we can see, someone with an admin role can access any endpoint:
@Test
@WithUserDetails(value = "admin")
public void whenAdminAccessUserEndpoint_thenOk() throws Exception {
mvc.perform(get("/user"))
.andExpect(status().isOk());
}
@Test
@WithUserDetails(value = "admin")
public void whenAdminAccessAdminSecuredEndpoint_thenIsOk() throws Exception {
mvc.perform(get("/admin"))
.andExpect(status().isOk());
}
@Test
@WithUserDetails(value = "admin")
public void whenAdminAccessDeleteSecuredEndpoint_thenIsOk() throws Exception {
mvc.perform(delete("/delete").content("{}"))
.andExpect(status().isOk());
}
5. Handling Deprecated csrf() and requiresChannel()
With recent updates in Spring Security, some methods used in HTTP security configuration, like csrf() and requiresChannel(), have deprecated versions that need adjustments when migrating to the latest Spring Boot versions. In this section, weβll discuss the updated way to configure these methods using lambda expressions.
The CSRF protection setup, previously done with a simple call to http.csrf().disable(), is now adjusted in lambda syntax:
http.csrf(csrf -> csrf.disable());
Using this approach, we specify CSRF configurations in a functional manner that aligns with recent Spring Security updates.
Similarly, the requiresChannel() method used to enforce HTTPS, has moved to a lambda-based format:
http.requiresChannel(channel -> channel.anyRequest().requiresSecure());
We can include both updated csrf and requiresChannel configurations in a single SecurityFilterChain bean. This setup enforces HTTPS across all requests and disables CSRF for simplicity:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(csrf->csrf.disable())
.requiresChannel(channel -> channel.anyRequest().requiresSecure())
.authorizeHttpRequests(authorizationManagerRequestMatcherRegistry ->
uthorizationManagerRequestMatcherRegistry.requestMatchers(HttpMethod.DELETE).hasRole("ADMIN")
.requestMatchers("/admin/**").hasAnyRole("ADMIN")
.requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")
.requestMatchers("/login/**").permitAll()
.anyRequest().authenticated())
.httpBasic(Customizer.withDefaults())
.sessionManagement(httpSecuritySessionManagementConfigurer ->
httpSecuritySessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return http.build();
}
6. Conclusion
In this article, we learned how to create a Spring Security configuration without using WebSecurityConfigureAdapter, and replace it while creating components for authentication, HTTP security, and Web security.
