You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Spring Boot中如何在拦截器获取匹配请求URI的REST方法及权限优化

解决方案:在Spring Boot中高效匹配REST接口权限

一、能否在AccessVoter中获取Spring即将调用的方法?

当然可以!Spring在请求映射完成后,会将匹配到的HandlerMethod(即Controller中对应的处理方法)存入HttpServletRequest的属性中,你可以直接在AccessVoter的vote方法里取出这个对象,进而读取方法上的@PostMapping/@GetMapping等注解路径。

具体实现步骤:

  1. 在AccessVoter的vote方法中,从FilterInvocation(或直接获取HttpServletRequest)里取出HandlerMethod
@Override
public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {
    FilterInvocation fi = (FilterInvocation) object;
    HttpServletRequest request = fi.getRequest();
    
    // 从request属性中获取匹配到的HandlerMethod
    Object handler = request.getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE);
    if (!(handler instanceof HandlerMethod)) {
        // 非Controller方法请求,按原有逻辑处理
        return ACCESS_ABSTAIN;
    }
    
    HandlerMethod handlerMethod = (HandlerMethod) handler;
    // 获取方法上的请求映射注解(兼容派生注解)
    RequestMapping requestMapping = handlerMethod.getMethodAnnotation(RequestMapping.class);
    if (requestMapping == null) {
        // 处理@PostMapping/@GetMapping等组合注解
        requestMapping = handlerMethod.getMethodAnnotation(PostMapping.class);
        requestMapping = requestMapping == null ? handlerMethod.getMethodAnnotation(GetMapping.class) : requestMapping;
        // 可继续扩展其他HTTP方法注解
    }
    
    if (requestMapping != null) {
        String[] pathTemplates = requestMapping.value();
        // 取第一个路径模板(根据实际业务调整,比如处理多路径配置)
        String targetPath = pathTemplates[0];
        // 仅查询数据库中该路径对应的权限条目
        List<Permission> permissions = permissionRepository.findByPathTemplate(targetPath);
        
        // 这里编写权限校验逻辑:对比用户权限与查询到的权限
        boolean hasPermission = checkUserPermission(authentication, permissions);
        return hasPermission ? ACCESS_GRANTED : ACCESS_DENIED;
    }
    
    return ACCESS_ABSTAIN;
}
  1. 关键点:数据库中存储的权限路径要和注解中的模板路径完全一致(比如/accounts/{id}/advisers/{adviserId}),而不是请求的实际路径,这样就能精准匹配,避免全量加载权限。

二、除AccessVoter外的替代方案

1. 自定义SecurityExpressionHandler(对应你提到的配置)

这种方案完全可以满足需求,不需要给每个REST方法加@PreAuthorize,而是全局配置表达式逻辑,通过自定义表达式处理器获取HandlerMethod并校验权限。

示例实现:

  • 自定义表达式处理器:
public class RestPermissionExpressionHandler extends DefaultWebSecurityExpressionHandler {
    @Autowired
    private PermissionRepository permissionRepository;

    @Override
    public EvaluationContext createEvaluationContext(Authentication authentication, FilterInvocation fi) {
        StandardEvaluationContext context = (StandardEvaluationContext) super.createEvaluationContext(authentication, fi);
        HttpServletRequest request = fi.getRequest();
        
        // 获取HandlerMethod,逻辑同AccessVoter
        Object handler = request.getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE);
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            RequestMapping requestMapping = getRequestMappingAnnotation(handlerMethod);
            if (requestMapping != null) {
                String pathTemplate = requestMapping.value()[0];
                List<Permission> requiredPermissions = permissionRepository.findByPathTemplate(pathTemplate);
                // 自定义表达式函数,用于权限校验
                context.registerFunction("hasRestPermission", this::checkRestPermission);
                context.setVariable("requiredPermissions", requiredPermissions);
            }
        }
        return context;
    }

    private Boolean checkRestPermission(Authentication auth, List<Permission> requiredPermissions) {
        // 实现权限匹配逻辑,比如检查用户权限是否包含所需权限
        return requiredPermissions.stream()
                .anyMatch(perm -> auth.getAuthorities().contains(new SimpleGrantedAuthority(perm.getCode())));
    }

    private RequestMapping getRequestMappingAnnotation(HandlerMethod handlerMethod) {
        // 兼容所有HTTP方法的派生注解
        RequestMapping mapping = handlerMethod.getMethodAnnotation(RequestMapping.class);
        if (mapping == null) {
            mapping = handlerMethod.getMethodAnnotation(PostMapping.class);
            mapping = mapping == null ? handlerMethod.getMethodAnnotation(GetMapping.class) : mapping;
            mapping = mapping == null ? handlerMethod.getMethodAnnotation(PutMapping.class) : mapping;
            mapping = mapping == null ? handlerMethod.getMethodAnnotation(DeleteMapping.class) : mapping;
        }
        return mapping;
    }
}
  • 在ResourceServer配置中使用:
@Configuration
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Autowired
    private RestPermissionExpressionHandler expressionHandler;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().access("hasRestPermission(authentication, requiredPermissions)")
                .expressionHandler(expressionHandler);
    }
}

2. 自定义前置Filter

在Spring Security过滤器链中,插入一个自定义Filter,在AccessDecisionManager之前执行:

  • 获取HandlerMethod并查询权限,将权限信息存入HttpServletRequest的属性中;
  • 后续AccessVoter直接从request中读取权限信息,减少重复逻辑。
    这种方案适合权限逻辑较复杂,需要和AccessVoter解耦的场景。

3. 启动预加载路径映射

在应用启动时,扫描所有Controller方法,将注解中的路径模板与方法关联并缓存(比如用Map<String, HandlerMethod>存储)。后续请求时,用Spring的PathMatcher匹配请求路径与缓存中的模板路径,快速找到对应的权限路径,再去数据库查询。
优点:避免每次请求都获取HandlerMethod,性能更高;缺点:需要处理动态权限变更的缓存刷新问题。

三、关键注意事项

  • 派生注解处理:要兼容@PostMapping/@GetMapping等所有@RequestMapping的派生注解;
  • 缓存优化:对查询到的权限结果添加缓存(本地缓存或Redis),避免频繁访问数据库;
  • 路径参数匹配:确保数据库中存储的是模板路径(带{id}),而非实际请求路径,保证匹配精准。

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

火山引擎 最新活动