Spring Security: 升级已弃用的 WebSecurityConfigurerAdapter
最后更新:2024年1月15日
1. 概述
Spring Security 允许通过扩展一个 WebSecurityConfigurerAdapter 类来定制 HTTP 安全性,用于诸如端点授权或身份验证管理器配置等功能。 然而,在最新版本中,Spring 弃用了这种方法,并鼓励基于组件的安全配置。
在本教程中,我们将学习如何在 Spring Boot 应用程序中替换这种弃用,并运行一些 MVC 测试。
更多阅读
2. 没有 WebSecurityConfigurerAdapter 的 Spring Security
我们通常会看到扩展 WebSecurityConfigureAdapter 类的 Spring HTTP 安全配置 类。
然而,从版本 5.7.0-M2 开始,Spring 弃用了 WebSecurityConfigureAdapter 的使用,并建议创建 没有它的配置。
让我们创建一个使用内存认证的示例 Spring Boot 应用程序来展示这种新的配置类型。
首先,我们将定义我们的配置类
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig {
// config
}
我们将添加 方法安全 注解以基于不同的角色启用处理。
2.1. 配置身份验证
使用 WebSecurityConfigureAdapter, 我们将使用 AuthenticationManagerBuilder 来设置我们的身份验证上下文。
现在,如果我们想避免弃用,我们可以定义一个 UserDetailsManager 或 UserDetailsService 组件
@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;
}
或者,鉴于我们的 UserDetailService,我们甚至可以设置一个 AuthenticationManager
@Bean
public AuthenticationManager authenticationManager(HttpSecurity http, BCryptPasswordEncoder bCryptPasswordEncoder, UserDetailService userDetailService)
throws Exception {
return http.getSharedObject(AuthenticationManagerBuilder.class)
.userDetailsService(userDetailsService)
.passwordEncoder(bCryptPasswordEncoder)
.and()
.build();
}
同样,如果使用 JDBC 或 LDAP 身份验证,这也将有效。
2.2. 配置 HTTP 安全性
更重要的是,如果我们想避免 HTTP 安全性的弃用,我们可以创建一个 SecurityFilterChain bean。
例如,假设我们想根据角色保护端点,并且仅允许匿名入口点进行登录。 我们还将限制任何删除请求到管理员角色。 我们将使用基本身份验证:
@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();
}
HTTP 安全性将构建一个 DefaultSecurityFilterChain 对象来加载请求匹配器和过滤器。
2.3. 配置 Web 安全性
对于 Web 安全性,我们现在可以使用回调接口 WebSecurityCustomizer。
我们将添加调试级别并忽略一些路径,例如图像或脚本
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return web -> web.debug(securityDebug).ignoring().requestMatchers("/css/**", "/js/**", "/img/**", "/lib/**", "/favicon.ico");
}
3. 端点控制器
现在我们将为我们的应用程序定义一个简单的 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;
}
}
如我们在定义 HTTP 安全性时提到的,我们将添加一个所有人都可以访问的通用 /login 端点,管理员和用户的特定端点,以及一个不需要角色但仍然需要身份验证的 /all 端点。
4. 测试端点
让我们将我们的新配置添加到使用 MVC 模拟器的 Spring Boot 测试 中,以测试我们的端点。
4.1. 测试匿名用户
匿名用户可以访问 /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());
}
此外,对于所有端点,除了/login之外,我们始终需要身份验证,就像对于/all端点一样。
4.2. 测试用户角色
用户角色可以访问通用端点以及我们为此角色授予的所有其他路径
@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());
}
值得注意的是,如果用户角色尝试访问管理员保护的端点,则用户将收到“禁止” (403) 错误。
相反,如果没有凭据的人,例如之前的匿名用户,将收到“未授权”错误 (401)。
4.3. 测试管理员角色
正如我们所见,具有管理员角色的人可以访问任何端点
@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. 处理已弃用的csrf() 和 requiresChannel()
随着 Spring Security 的最新更新,在 HTTP 安全配置中使用的一些方法,例如 csrf() 和 requiresChannel(),已经弃用,需要在迁移到最新的 Spring Boot 版本时进行调整。在本节中,我们将讨论使用 lambda 表达式配置这些方法的更新方式。
CSRF 保护设置,以前通过简单的调用 http.csrf().disable() 来完成,现在使用 lambda 语法进行了调整
http.csrf(csrf -> csrf.disable());
使用这种方法,我们以与 Spring Security 最新更新保持一致的函数式方式指定 CSRF 配置。
同样,用于强制执行 HTTPS 的 requiresChannel() 方法也已移动到基于 lambda 的格式
http.requiresChannel(channel -> channel.anyRequest().requiresSecure());
我们可以在单个 SecurityFilterChain bean 中包含更新的 csrf 和 requiresChannel 配置。此设置强制所有请求使用 HTTPS,并禁用 CSRF 以简化操作
@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. 结论
在本文中,我们学习了如何在不使用 WebSecurityConfigureAdapter 的情况下创建 Spring Security 配置,并在创建身份验证、HTTP 安全性和 Web 安全性组件时替换它。
支持本文的代码可在 GitHub 上获取。 一旦你以 Baeldung Pro 会员 身份登录,就开始学习并在项目上进行编码。















