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]

参考资料
[1]#I8I2YL【权限设计】 提供 Sa-Token 平替版本,通过插件机制实现: https://gitee.com/log4j/pig/issues/I8I2YL

[2]源码:PIG NEXT: https://github.com/pig-mesh/pig/tree/next-sa

扫码领红包

微信赞赏支付宝扫码领红包

发表回复

后才能评论