Spring Boot 3.3 即 Spring Security 6.3 中 在授权方面引入了几个显著的变化。在本文中,我们将探讨这些新变化,并通过示例代码来展示其具体应用。
1. 动态注解参数
Spring Security 的方法安全性支持元注解。我们可以利用元注解根据应用的使用情况提高代码的可读性。例如,我们可以将 @PreAuthorize("hasRole('USER')")
简化为如下形式:
@Target({ ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasRole('USER')")
public @interface IsUser {
String[] value();
}
接下来,我们可以在业务代码中使用这个 @IsUser
注解:
@Service
public class DemoService {
@IsUser
public Data demoService() {
return "data";
}
}
假设我们还有一个 ADMIN
角色。我们可以为这个角色创建一个名为 @IsAdmin
的注解。然而,这样做会显得冗余。更合适的方法是使用元注解作为模板,并将角色作为注解参数。Spring Security 6.3 引入了定义此类元注解的能力。我们用一个具体的例子来演示这一点:
首先,我们需要定义一个 PrePostTemplateDefaults
bean:
@Bean
PrePostTemplateDefaults prePostTemplateDefaults() {
return new PrePostTemplateDefaults();
}
这个 bean 定义是模板解析所必需的。
接下来,我们定义一个可以接受 USER
和 ADMIN
角色的 @CustomHasAnyRole
元注解:
@Target({ ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasAnyRole({value})")
public @interface CustomHasAnyRole {
String[] value();
}
我们可以通过提供角色来使用这个元注解:
@Service
public class DemoService {
private final List<Message> messages;
public DemoService() {
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";
}
}
再进阶一下,如 PIG 微服务框架中 @HasPermission 注解
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("@pms.hasPermission('{value}'.split(','))")
public @interface HasPermission {
/**
* 权限字符串
* @return {@link String[] }
*/
String[] value();
}
接口上的内容就变的更为清晰明了
@PreAuthorize("@pms.hasPermission('job_sys_job_add')")
↓
@HasPermission("job_sys_job_add")
2. 保护返回值(数据权限)
Spring Security 6.3 的另一个强大新特性是使用 @AuthorizeReturnObject
注解保护域对象的能力。这一增强功能允许对方法返回的对象进行授权检查,从而确保只有授权用户才能访问特定的域对象。
我们用一个例子来演示这一点。假设我们有以下包含 iban
和 balance
字段的 Account
类。要求只有具有读取权限的用户才能检索账户余额。
public class Account {
private String iban;
private Double balance;
// 构造函数
public String getIban() {
return iban;
}
@PreAuthorize("hasAuthority('read')")
public Double getBalance() {
return balance;
}
}
接下来,我们定义返回账户实例的 AccountService
类:
@Service
public class AccountService {
@AuthorizeReturnObject
public Optional<Account> getAccountByIban(String iban) {
return Optional.of(new Account("XX1234567809", 2345.6));
}
}
在上面的代码段中,我们使用了 @AuthorizeReturnObject
注解。Spring Security 确保只有具有读取权限的用户才能访问 Account
实例。
3. 403 错误处理
在上一节中,我们讨论了使用 @AuthorizeReturnObject
注解保护域对象。一旦启用,未经授权的访问会导致 AccessDeniedException
。Spring Security 6.3 提供了 MethodAuthorizationDeniedHandler
接口来处理授权失败。
我们用一个例子来演示这一点。假设我们在第 2 节的例子中保护了 IBAN
字段,并要求读取权限。然而,我们打算为未经授权的访问提供一个掩码值,而不是返回 AccessDeniedException
。
我们定义 MethodAuthorizationDeniedHandler
接口的实现类:
@Component
public class MaskMethodAuthorizationDeniedHandler implements MethodAuthorizationDeniedHandler {
@Override
public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
return "****";
}
}
在上面的代码段中,如果存在 AccessDeniedException
,我们提供了一个掩码值。这个处理程序类可以在 getIban()
方法中使用,如下所示:
@PreAuthorize("hasAuthority('read')")
@HandleAuthorizationDenied(handlerClass=MaskMethodAuthorizationDeniedHandler.class)
public String getIban() {
return iban;
}
功能预告
基于 Spring Boot 3.3 灵活的注解能力,PIG 微服务开发平台新版本实现了 #I8I2YL【权限设计】 提供 Sa-Token 平替版本,通过插件机制实现[1] 的功能目标。
在保持一套代码、一个架构、一个授权逻辑、业务代码无需任何改动的前提下,整体架构不变,并且兼容 Spring Authorization Server 和 Sa-Token OAuth 2.0 权限框架模型,包括资源服务器和认证服务器。
源码:PIG NEXT[2]
[2]源码:PIG NEXT: https://github.com/pig-mesh/pig/tree/next-sa
微信赞赏
支付宝扫码领红包