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

如何自定义Spring Security的@PreAuthorize注解实现权限校验

解决Spring Security自定义权限注解的SPEL属性传递问题

正确实现自定义权限注解

你遇到的核心问题是SPEL表达式无法直接引用自定义注解的value属性——默认上下文没有暴露该变量。正确的做法是通过SPEL的@annotation语法获取当前方法上的自定义注解实例,再提取其value属性。

修改你的AuthorityRequired注解如下:

import org.springframework.security.access.prepost.PreAuthorize;
import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
// 通过@annotation获取当前方法的AuthorityRequired注解实例,再取其value属性
@PreAuthorize("@tenantSecurityService.hasAuthority(@annotation(com.yourpackage.AuthorityRequired).value)")
public @interface AuthorityRequired {
    String value();
}

关键说明

  • @annotation(com.yourpackage.AuthorityRequired)会告诉SPEL解析器去当前被注解的方法上查找AuthorityRequired注解实例
  • 直接通过.value访问该注解的属性,传递给你的tenantSecurityService.hasAuthority()方法

这样你就可以像期望的那样使用注解:

@PostMapping
@AuthorityRequired(Authorities.PRODUCTS_WRITE)
public Product createProduct(@RequestBody @Valid ProductCreateRequest createRequest) {
    return productService.createProduct(createRequest);
}

其他便捷的权限实现思路

如果需要更灵活的权限控制,还可以考虑以下方案:

1. 扩展Spring Security表达式根

自定义SecurityExpressionRoot,添加自定义的权限校验方法,这样可以直接在@PreAuthorize中使用更简洁的表达式:

public class CustomSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {
    private Object filterObject;
    private Object returnObject;
    private TenantSecurityService tenantSecurityService;

    public CustomSecurityExpressionRoot(Authentication authentication) {
        super(authentication);
    }

    // 自定义方法,直接接收权限参数
    public boolean hasTenantAuthority(String authority) {
        return tenantSecurityService.hasAuthority(authority);
    }

    // 注入自定义服务
    public void setTenantSecurityService(TenantSecurityService tenantSecurityService) {
        this.tenantSecurityService = tenantSecurityService;
    }

    // 实现MethodSecurityExpressionOperations的必要方法
    @Override
    public void setFilterObject(Object filterObject) {
        this.filterObject = filterObject;
    }

    @Override
    public Object getFilterObject() {
        return filterObject;
    }

    @Override
    public void setReturnObject(Object returnObject) {
        this.returnObject = returnObject;
    }

    @Override
    public Object getReturnObject() {
        return returnObject;
    }

    @Override
    public Object getThis() {
        return this;
    }
}

然后配置方法安全表达式处理器:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
    @Autowired
    private TenantSecurityService tenantSecurityService;

    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() {
        DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
        handler.setSecurityExpressionRootFactory(authentication -> {
            CustomSecurityExpressionRoot root = new CustomSecurityExpressionRoot(authentication);
            root.setTenantSecurityService(tenantSecurityService);
            return root;
        });
        return handler;
    }
}

之后就可以这样使用(直接引用静态常量避免字符串拼接):

@PreAuthorize("hasTenantAuthority(T(com.yourpackage.Authorities).PRODUCTS_WRITE)")

2. 批量权限元注解

如果有一组常用的权限组合,可以定义更具体的元注解,进一步减少重复代码:

@AuthorityRequired(Authorities.PRODUCTS_WRITE)
public @interface ProductWritePermission {}

在方法上直接使用:

@PostMapping
@ProductWritePermission
public Product createProduct(...) { ... }

3. AOP实现权限校验

如果不想依赖Spring Security的注解体系,可以用Spring AOP自定义切面,拦截带有AuthorityRequired注解的方法执行权限校验:

@Aspect
@Component
public class AuthorityAspect {
    @Autowired
    private TenantSecurityService tenantSecurityService;

    @Around("@annotation(authorityRequired)")
    public Object checkAuthority(ProceedingJoinPoint joinPoint, AuthorityRequired authorityRequired) throws Throwable {
        String requiredAuthority = authorityRequired.value();
        if (!tenantSecurityService.hasAuthority(requiredAuthority)) {
            throw new AccessDeniedException("Insufficient authority");
        }
        return joinPoint.proceed();
    }
}

这种方式更轻量,但需要自行处理异常和认证上下文的获取。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 03:02:24