1. 引言
Spring Security 6.3 带来了一系列框架层面的安全增强。本文将重点介绍其中几个关键特性,包括它们的优势和实际用法。
2. 被动式 JDK 序列化支持
Spring Security 6.3 引入了被动式 JDK 序列化支持。在深入探讨前,我们先理解这个设计背后的问题和挑战。
2.1. Spring Security 序列化设计
在 6.3 版本之前,Spring Security 对跨版本的 JDK 序列化/反序列化有严格限制。这个设计是框架刻意为之,旨在确保安全性和稳定性。核心逻辑是:防止使用不同版本 Spring Security 反序列化对象时出现兼容性问题和安全漏洞。
关键设计点在于全局使用统一的 serialVersionUID
。Java 序列化机制通过 serialVersionUID
验证加载类与序列化对象的匹配性。Spring Security 为每个发布版本维护全局唯一的 serialVersionUID
,确保不同版本间无法互相反序列化,从而形成版本屏障。
例如 SecurityContextImpl
类的序列化版本包含特定版本的 serialVersionUID
。当尝试用不同版本 Spring Security 反序列化时,serialVersionUID
不匹配会导致失败。
2.2. 序列化设计带来的挑战
虽然这个设计强化了安全性,但也带来了一些实际开发中的痛点:
- 金丝雀发布升级问题:当 Spring Security 版本变更时,持久化的会话信息无法反序列化,可能导致用户需要重新登录
- RMI 架构兼容性:在基于 RMI 的架构中,如果客户端和服务端使用不同 Spring Security 版本,远程调用会因
InvalidClassException
失败
2.3. 临时解决方案
常见的变通方案包括:
- 替换序列化库:使用 Jackson 等替代 JDK 序列化,将对象转为 JSON 再序列化
- 自定义序列化:继承 Spring Security 类(如
Authentication
),通过readObject
/writeObject
实现自定义序列化
2.4. Spring Security 6.3 的序列化改进
6.3 版本实现了向前兼容的序列化机制:类序列化时会与前一个次版本进行兼容性检查。这确保升级到新版本时,Spring Security 类的反序列化可以无缝进行。
3. 授权机制增强
Spring Security 6.3 在授权机制上引入了多项重要改进。
3.1. 注解参数化
Spring Security 的方法安全支持元注解。我们可以基于业务场景创建可读性更强的注解。例如将 @PreAuthorize("hasRole('USER')")
简化为:
@Target({ ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasRole('USER')")
public @interface IsUser {
String[] value();
}
然后在业务代码中使用:
@Service
public class MessageService {
@IsUser
public Message readMessage() {
return "Message";
}
}
但为每个角色创建独立注解(如 @IsAdmin
)会显得冗余。Spring Security 6.3 支持参数化元注解,实现更灵活的模板化。具体步骤:
定义模板解析 Bean:
@Bean PrePostTemplateDefaults prePostTemplateDefaults() { return new PrePostTemplateDefaults(); }
创建参数化元注解:
@Target({ ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @PreAuthorize("hasAnyRole({value})") public @interface CustomHasAnyRole { String[] value(); }
在业务代码中动态指定角色:
@Service public class MessageService { private final List<Message> messages; public MessageService() { messages = new ArrayList<>(); messages.add(new Message(1, "Message 1")); } @CustomHasAnyRole({"'USER'", "'ADMIN'"}) public Message readMessage(Integer id) { return messages.get(0); } @CustomHasAnyRole("'ADMIN'") public String writeMessage(Message message) { return "Message Written"; } @CustomHasAnyRole({"'ADMIN'"}) public String deleteMessage(Integer id) { return "Message Deleted"; } }
3.2. 返回值安全控制
Spring Security 6.3 新增 @AuthorizeReturnObject
注解,支持对方法返回的领域对象进行细粒度安全控制。这确保只有授权用户才能访问特定领域对象。
示例场景:Account
类的 balance
字段只允许有 read
权限的用户访问:
public class Account {
private String iban;
private Double balance;
// Constructor
public String getIban() {
return iban;
}
@PreAuthorize("hasAuthority('read')")
public Double getBalance() {
return balance;
}
}
在服务层使用 @AuthorizeReturnObject
保护返回对象:
@Service
public class AccountService {
@AuthorizeReturnObject
public Optional<Account> getAccountByIban(String iban) {
return Optional.of(new Account("XX1234567809", 2345.6));
}
}
3.3. 错误处理优化
使用 @AuthorizeReturnObject
时,未授权访问会抛出 AccessDeniedException
。Spring Security 6.3 提供 MethodAuthorizationDeniedHandler
接口实现自定义错误处理。
示例场景:对未授权访问返回脱敏值而非异常:
实现自定义处理器:
@Component public class MaskMethodAuthorizationDeniedHandler implements MethodAuthorizationDeniedHandler { @Override public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) { return "****"; } }
在方法上应用处理器:
@PreAuthorize("hasAuthority('read')") @HandleAuthorizationDenied(handlerClass=MaskMethodAuthorizationDeniedHandler.class) public String getIban() { return iban; }
4. 弱密码检测
Spring Security 6.3 内置弱密码检测功能,通过比对 pwnedpasswords.com 数据库验证密码安全性。可在用户注册时实时检测密码是否已泄露。
实现步骤:
定义检测 Bean:
@Bean public HaveIBeenPwnedRestApiPasswordChecker passwordChecker() { return new HaveIBeenPwnedRestApiPasswordChecker(); }
在注册接口中使用:
@RestController @RequestMapping("/register") public class RegistrationController { private final HaveIBeenPwnedRestApiPasswordChecker haveIBeenPwnedRestApiPasswordChecker; @Autowired public RegistrationController(HaveIBeenPwnedRestApiPasswordChecker haveIBeenPwnedRestApiPasswordChecker) { this.haveIBeenPwnedRestApiPasswordChecker = haveIBeenPwnedRestApiPasswordChecker; } @PostMapping public String register(@RequestParam String username, @RequestParam String password) { CompromisedPasswordDecision compromisedPasswordDecision = haveIBeenPwnedRestApiPasswordChecker.checkPassword(password); if (compromisedPasswordDecision.isCompromised()) { throw new IllegalArgumentException("Compromised Password."); } // ... return "User registered successfully"; } }
5. OAuth 2.0 令牌交换授权
Spring Security 6.3 支持 OAuth 2.0 令牌交换授权(RFC 8693),允许客户端在保留用户身份的前提下交换令牌。这特别适用于资源服务器需要扮演客户端获取新令牌的场景(如模拟用户操作)。
典型场景:
loan-service
资源服务器需要调用loan-product-service
- 两个服务要求不同的
aud
声明(受众) - 原始令牌无法直接用于目标服务
解决方案:loan-service
作为客户端,通过令牌交换获取适用于 loan-product-service
的新令牌,同时保留原始用户身份。
Spring Security 6.3 提供了 TokenExchangeOAuth2AuthorizedClientProvider
实现此功能。
6. 总结
Spring Security 6.3 带来了多项重要改进: ✅ 授权框架增强(参数化注解、返回值安全控制、自定义错误处理) ✅ 被动式 JDK 序列化支持 ✅ 内置弱密码检测 ✅ OAuth 2.0 令牌交换支持
这些特性显著提升了框架的易用性和安全性,特别是在微服务架构和复杂授权场景下。完整示例代码可在 GitHub 获取。