Spring Security: Upgrading the Deprecated WebSecurityConfigurerAdapter – Spring Security 升级废弃的WebSecurityConfigurerAdapter

最后修改: 2022年 7月 31日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

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.

Spring Security允许通过扩展WebSecurityConfigurerAdapter类来定制HTTP安全的功能,如端点授权或认证管理器配置。然而,在最近的版本中,Spring放弃了这种方法,鼓励采用基于组件的安全配置。

In this tutorial, we’ll learn how we can replace this deprecation in a Spring Boot application and run some MVC tests.

在本教程中,我们将学习如何在Spring Boot应用程序中替换这一缺失,并运行一些MVC测试。

2. Spring Security Without the WebSecurityConfigurerAdapter

2.没有WebSecurityConfigurerAdapter的Spring Security

We commonly see Spring HTTP security configuration classes that extend a WebSecurityConfigureAdapter class.

我们经常看到Spring HTTP安全配置类扩展WebSecurityConfigureAdapter类。

However, beginning with version 5.7.0-M2, Spring deprecates the use of WebSecurityConfigureAdapter and suggests creating configurations without it.

然而,从5.7.0-M2版本开始,Spring不再使用WebSecurityConfigureAdapter,并建议创建配置而不使用

Let’s create an example Spring Boot application using in-memory authentication to show this new type of configuration.

让我们创建一个使用内存认证的Spring Boot应用实例来展示这种新型配置。

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.

我们将添加method security注释,以实现基于不同角色的处理。

2.1. Configure Authentication

2.1.配置认证

With the WebSecurityConfigureAdapter, we’ll use an AuthenticationManagerBuilder to set our authentication context.

通过WebSecurityConfigureAdapter,我们将使用AuthenticationManagerBuilder来设置我们的认证环境。

Now, if we want to avoid deprecation, we can define a UserDetailsManager or UserDetailsService component:

现在,如果我们想避免废弃,我们可以定义一个UserDetailsManagerUserDetailsService组件:

@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:

或者,鉴于我们的UserDetailService,我们甚至可以设置一个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.

同样地,如果我们使用JDBC或LDAP认证,这也会有效。

2.2. Configure HTTP Security

2.2 配置HTTP安全

More importantly, if we want to avoid deprecation for HTTP security, we can create a SecurityFilterChain bean.

更重要的是,如果我们想避免HTTP安全的废弃,我们可以创建一个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.

HTTP安全将建立一个DefaultSecurityFilterChain对象来加载请求匹配器和过滤器。

2.3. Configure Web Security

2.3.配置网络安全

For Web security, we can now use the callback interface WebSecurityCustomizer.

对于网络安全,我们现在可以使用回调接口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

3.端点控制器

Now we’ll define a simple REST controller class for our application:

现在我们将为我们的应用程序定义一个简单的REST控制器类。

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

正如我们之前在定义HTTP安全时提到的,我们将添加一个任何人都可以访问的通用/login端点、针对管理员和用户的特定端点,以及一个不受角色保护、但仍需要认证的/all端点。

4. Test Endpoints

4.测试端点

Let’s add our new configuration to a Spring Boot Test using an MVC mock to test our endpoints.

让我们将我们的新配置添加到Spring Boot测试中,使用MVC mock来测试我们的端点。

4.1. Test Anonymous Users

4.1.测试匿名用户

Anonymous users can access the /login endpoint. If they try to access something else, they’ll be unauthorized (401):

匿名用户可以访问/login端点。如果他们试图访问其他东西,他们会被授权(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.

此外,对于除/login以外的所有端点,我们总是要求认证,就像对/all端点一样。

4.2. Test User Role

4.2 测试用户角色

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.

值得注意的是,如果一个用户角色试图访问一个管理员安全的端点,用户会得到一个 “禁止”(403)错误。

Conversely, someone with no credentials, like an anonymous in the previous example, will get an “unauthorized” error (401).

相反,没有证书的人,如前面例子中的匿名者,将得到一个 “未授权 “的错误(401)。

4.3. Test Admin Role

4.3.测试管理员角色

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

5.总结

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.

在这篇文章中,我们学习了如何在不使用WebSecurityConfigureAdapter,的情况下创建Spring Security配置,并在创建认证、HTTP安全和Web安全的组件时替换它。

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

一如既往,我们可以在GitHub上找到工作代码的例子