You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Security:从废弃AccessDecisionVoter迁移至AuthorizationManager方案咨询

Spring Security 6:AccessDecisionVoter到AuthorizationManager迁移方案(针对@Secured多Voter场景)

核心思路

Spring Security 6废弃了AccessDecisionVoter和AccessDecisionManager,改用AuthorizationManager统一处理授权逻辑。针对你的场景,需要:

  1. 将每个自定义Voter转换为独立的AuthorizationManager实现
  2. 实现组合式AuthorizationManager复刻原CollectingUnanimousBasedAccessDecisionManager的决策规则
  3. 配置方法安全,把组合管理器绑定到@Secured注解的处理流程中

步骤1:迁移单个Voter到AuthorizationManager

把原有的AbstractAuthorityVoter和ServiceCallVoter分别转换成AuthorizationManager<MethodInvocation>实现,将原vote()逻辑迁移到check()方法,返回AuthorizationDecision表示授权结果。

示例1:迁移AbstractAuthorityVoter

public class AuthorityAuthorizationManager implements AuthorizationManager<MethodInvocation> {
    private final String rolePrefix;

    public AuthorityAuthorizationManager(String rolePrefix) {
        this.rolePrefix = rolePrefix;
    }

    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation invocation) {
        // 提取@Secured注解的权限配置
        Secured secured = invocation.getMethod().getAnnotation(Secured.class);
        if (secured == null) {
            return new AuthorizationDecision(true); // 无注解时默认允许,可根据原逻辑调整
        }

        Authentication auth = authentication.get();
        if (auth == null || !auth.isAuthenticated()) {
            return new AuthorizationDecision(false);
        }

        // 匹配权限(复刻原AbstractAuthorityVoter的逻辑)
        boolean hasMatchingAuthority = Arrays.stream(secured.value())
                .map(authStr -> rolePrefix + authStr)
                .anyMatch(authority -> auth.getAuthorities().contains(new SimpleGrantedAuthority(authority)));
        
        return new AuthorizationDecision(hasMatchingAuthority);
    }

    // 按需实现supports方法,确保只处理MethodInvocation类型的授权请求
    @Override
    public boolean supports(Class<?> clazz) {
        return MethodInvocation.class.isAssignableFrom(clazz);
    }
}

示例2:迁移ServiceCallVoter

public class ServiceCallAuthorizationManager implements AuthorizationManager<MethodInvocation> {
    private final YourService yourService; // 原Voter依赖的服务

    public ServiceCallAuthorizationManager(YourService yourService) {
        this.yourService = yourService;
    }

    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation invocation) {
        // 复刻原ServiceCallVoter的投票逻辑
        boolean isPermitted = yourService.validatePermission(authentication.get(), invocation.getMethod(), invocation.getArguments());
        return new AuthorizationDecision(isPermitted);
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return MethodInvocation.class.isAssignableFrom(clazz);
    }
}

步骤2:实现组合式AuthorizationManager(复刻CollectingUnanimousBased逻辑)

创建自定义组合管理器,模拟原CollectingUnanimousBasedAccessDecisionManager的"所有Voter同意则通过"规则:

public class UnanimousCompositeAuthorizationManager implements AuthorizationManager<MethodInvocation> {
    private final List<AuthorizationManager<MethodInvocation>> authorizationManagers;

    public UnanimousCompositeAuthorizationManager(List<AuthorizationManager<MethodInvocation>> authorizationManagers) {
        this.authorizationManagers = authorizationManagers;
    }

    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation invocation) {
        // 所有子管理器都返回允许才通过,否则拒绝
        boolean allGranted = authorizationManagers.stream()
                .map(manager -> manager.check(authentication, invocation))
                .allMatch(AuthorizationDecision::isGranted);
        
        return new AuthorizationDecision(allGranted);
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return MethodInvocation.class.isAssignableFrom(clazz);
    }
}

如果原决策逻辑是"无反对票即通过"(允许弃权),可调整逻辑为:

boolean hasDeny = authorizationManagers.stream()
        .map(manager -> manager.check(authentication, invocation))
        .anyMatch(decision -> !decision.isGranted());
return new AuthorizationDecision(!hasDeny);

步骤3:配置方法安全,绑定@Secured注解处理

在方法安全配置类中,启用@Secured支持,并将组合管理器绑定到注解的处理流程:

@Configuration
@EnableMethodSecurity(securedEnabled = true) // 必须开启securedEnabled
public class MethodSecurityConfig {

    // 注册单个授权管理器
    @Bean
    public AuthorityAuthorizationManager authorityAuthorizationManager() {
        return new AuthorityAuthorizationManager("ROLE_"); // 原AbstractAuthorityVoter的前缀
    }

    @Bean
    public ServiceCallAuthorizationManager serviceCallAuthorizationManager(YourService yourService) {
        return new ServiceCallAuthorizationManager(yourService);
    }

    // 注册组合授权管理器
    @Bean
    public AuthorizationManager<MethodInvocation> securedAuthorizationManager(
            AuthorityAuthorizationManager authorityManager,
            ServiceCallAuthorizationManager serviceCallManager) {
        return new UnanimousCompositeAuthorizationManager(
                Arrays.asList(authorityManager, serviceCallManager)
        );
    }

    // 配置@Secured注解使用自定义组合管理器
    @Bean
    public MethodSecurityConfigurer<?> methodSecurityConfigurer(AuthorizationManager<MethodInvocation> securedAuthorizationManager) {
        return new GlobalMethodSecurityConfiguration() {
            @Override
            protected void configure(MethodSecurityConfigurer<?> config) throws Exception {
                config.secured()
                        .authorizationManager(securedAuthorizationManager); // 替换默认的@Secured处理逻辑
            }
        };
    }
}

关键注意事项

  • 原AccessDecisionVoter的supports(ConfigAttribute)逻辑,可通过AuthorizationManager的supports(Attribute)方法实现,按需过滤处理的注解属性
  • 如果你的服务中同时存在@PreAuthorize和@Secured,可分别配置对应的AuthorizationManager,互不干扰
  • 测试时需验证组合逻辑是否与原CollectingUnanimousBasedAccessDecisionManager的行为完全一致,避免授权逻辑变更

内容的提问来源于stack exchange,提问作者Ruth

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 00:44:52