Course – LSS – NPI (cat=Security/Spring Security)
announcement - icon

I just announced the new Learn Spring Security course, including the full material focused on the new OAuth2 stack in Spring Security:

>> CHECK OUT THE COURSE

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:

Deprecated Classes in Spring

Explore deprecated classes in Spring and Spring Boot.

Warning: "The type WebMvcConfigurerAdapter is deprecated"

Find out how to fix the WebMvcConfigurerAdapter warning in Spring

Default Password Encoder in Spring Security 5

Learn how to avoid the IllegalArgumentException when migrating plaintext passwords to Spring Security 5

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. 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.

As always, we can find working code examples over on GitHub.

Course – LSS (cat=Security/Spring Security)

I just announced the new Learn Spring Security course, including the full material focused on the new OAuth2 stack in Spring Security:

>> CHECK OUT THE COURSE
res – Security (video) (cat=Security/Spring Security)
2 Comments
Oldest
Newest
Inline Feedbacks
View all comments
Comments are closed on this article!