Spring Boot中如何在拦截器获取匹配请求URI的REST方法及权限优化
解决方案:在Spring Boot中高效匹配REST接口权限
一、能否在AccessVoter中获取Spring即将调用的方法?
当然可以!Spring在请求映射完成后,会将匹配到的HandlerMethod(即Controller中对应的处理方法)存入HttpServletRequest的属性中,你可以直接在AccessVoter的vote方法里取出这个对象,进而读取方法上的@PostMapping/@GetMapping等注解路径。
具体实现步骤:
- 在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; }
- 关键点:数据库中存储的权限路径要和注解中的模板路径完全一致(比如
/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




