Expand Authors Top

If you have a few years of experience in the Java ecosystem and you’d like to share that with the community, have a look at our Contribution Guidelines.

November Discount Launch 2022 – Top
We’re finally running a Black Friday launch. All Courses are 30% off until next Friday:

>> GET ACCESS NOW

NPI – Frontegg – Security – (partner)
announcement - icon User management is very complex, when implemented properly. No surprise here.

Not having to roll all of that out manually, but instead integrating a mature, fully-fledged solution - yeah, that makes a lot of sense.
That's basically what Frontegg is - User Management for your application. It's focused on making your app scalable, secure and enjoyable for your users.
From signup to authentication, it supports simple scenarios all the way to complex and custom application logic.

Have a look:

>> Elegant User Management, Tailor-made for B2B SaaS

Expanded Audience – Frontegg – Security (partner)
announcement - icon User management is very complex, when implemented properly. No surprise here.

Not having to roll all of that out manually, but instead integrating a mature, fully-fledged solution - yeah, that makes a lot of sense.
That's basically what Frontegg is - User Management for your application. It's focused on making your app scalable, secure and enjoyable for your users.
From signup to authentication, it supports simple scenarios all the way to complex and custom application logic.

Have a look:

>> Elegant User Management, Tailor-made for B2B SaaS

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.

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:

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, 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 authManager(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()
      .disable()
      .authorizeRequests()
      .antMatchers(HttpMethod.DELETE)
      .hasRole("ADMIN")
      .antMatchers("/admin/**")
      .hasAnyRole("ADMIN")
      .antMatchers("/user/**")
      .hasAnyRole("USER", "ADMIN")
      .antMatchers("/login/**")
      .anonymous()
      .anyRequest()
      .authenticated()
      .and()
      .httpBasic()
      .and()
      .sessionManagement()
      .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()
      .antMatchers("/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.

November Discount Launch 2022 – Bottom
We’re finally running a Black Friday launch. All Courses are 30% off until next Friday:

>> GET ACCESS NOW

Security footer banner
2 Comments
Oldest
Newest
Inline Feedbacks
View all comments
Comments are closed on this article!