准备升级 Spring Security 6:配置风格全变了
Spring Security 6.0 要在 2022 年 11 月随 Spring Boot 3.0 一起 GA,但我们不想等上线了再手忙脚乱。10 月初我就拉了 6.0 的候选版本做预研,最大的冲击是:WebSecurityConfigurerAdapter 被废弃了,那套我们写了好几年的配置模板全要重写。把关键变更和 OAuth2/JWT 接法整理如下。
变更一:WebSecurityConfigurerAdapter 没了
老写法是继承适配器,重写 configure 方法:
// 老写法(Spring Security 5.x)
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and().formLogin();
}
}
6.0 里适配器被标记为废弃(后续版本移除),改为直接声明一个 SecurityFilterChain Bean,用 Lambda DSL 配置。好处是配置变显式、可组合,不再依赖继承:
// 新写法(Spring Security 6.0)
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated())
.formLogin(Customizer.withDefaults());
return http.build();
}
}
注意两处改名:authorizeRequests → authorizeHttpRequests,antMatchers → requestMatchers。后者支持 Ant、MVC 和正则多种匹配,更统一。
变更二:Lambda DSL 让配置可终结
新风格用 Lambda 替代链式 .and()。老式 .and() 链式很容易漏写、嵌套错;Lambda 里每个配置块独立,IDE 能帮你检查括号闭合:
http
.csrf(csrf -> csrf.disable()) // 无状态 API 常关 CSRF
.sessionManagement(sm -> sm.sessionCreationPolicy(STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.anyRequest().authenticated())
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
变更三:OAuth2 资源Ubuntu 服务器与 JWT 集成
我们对外暴露的 API 用 JWT 做Bearer 鉴权。6.0 里配置资源服务器读 JWT 非常简洁,关键是指定 JWT 解码器,可以从授权服务器的元数据自动获取公钥:
@Bean
public JwtDecoder jwtDecoder() {
// 从授权服务器的 JWK Set 端点获取公钥
return NimbusJwtDecoder
.withIssuerLocation("https://auth.xxx.com/realms/order")
.build();
}
// 在 filterChain 里启用
http.oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> {}));
拿到 Jwt 后,从 claims 里提取权限映射到 GrantedAuthority。我们自定义了一个 JwtAuthenticationConverter:
@Bean
public JwtAuthenticationConverter jwtAuthConverter() {
JwtGrantedAuthoritiesConverter conv = new JwtGrantedAuthoritiesConverter();
conv.setAuthoritiesClaimName("roles");
conv.setAuthorityPrefix("ROLE_"); // 让 hasRole("ADMIN") 对应 roles=["ADMIN"]
JwtAuthenticationConverter c = new JwtAuthenticationConverter();
c.setJwtGrantedAuthoritiesConverter(conv);
return c;
}
实测一个小坑:6.0 默认对 JWT 的 iss 校验更严格,如果授权服务器 issuer 地址末尾带不带 / 要和 withIssuerLocation 完全一致,否则启动就 401。我们在这个细节上调试了半小时。
迁移时要查的几处
| 旧(5.x) | 新(6.0) |
|---|---|
| extends WebSecurityConfigurerAdapter | 声明 SecurityFilterChain Bean |
| authorizeRequests() | authorizeHttpRequests() |
| antMatchers() | requestMatchers() |
| .and() 链式 | Lambda DSL |
小结
WebSecurityConfigurerAdapter被废弃,改为声明SecurityFilterChainBean,配置显式可组合。- Lambda DSL 替代
.and(),可读性更好,IDE 友好。 - OAuth2 资源服务器 + JWT 通过
oauth2ResourceServer().jwt()启用,解码器从 JWK 端点自动取公钥。 - 权限映射注意
ROLE_前缀与 issuer 严格匹配两个细节。
这次预研最大的感受:Security 6 不是简单改名,而是把"靠继承配置"改成了"靠组合声明"。短期要改不少模板代码,但长期看,不再有那个必须记住要 override 哪个方法的适配器基类,配置意图反而更清楚了。