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

如何根据调用方式差异化保护API端点,实现API密钥调用特殊角色校验

针对API调用方式的差异化安全校验方案

结合你提到的需求(仅API密钥调用时需校验MYROLE,密码登录用户调用无需该角色),以下是几种实用的实现方案,均基于Spring Security生态(你提到@Secured注解,默认是Spring环境):

方案一:自定义方法安全表达式(推荐,注解式精准控制)

通过扩展Spring Security的方法安全表达式,直接在端点注解中区分调用方式,替代原生@Secured实现差异化校验。

实现步骤

  1. 自定义表达式根类:添加判断调用方式的方法,识别当前请求是API密钥认证还是密码用户认证
public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {
    public CustomMethodSecurityExpressionRoot(Authentication authentication) {
        super(authentication);
    }

    // 判断当前是否为API密钥调用(需匹配你实际的API密钥认证Token类型)
    public boolean isApiKeyCall() {
        return authentication instanceof ApiKeyAuthenticationToken;
    }

    // 实现接口必填的空方法,无需额外逻辑
    @Override public void setFilterObject(Object filterObject) {}
    @Override public Object getFilterObject() { return null; }
    @Override public void setReturnObject(Object returnObject) {}
    @Override public Object getReturnObject() { return null; }
    @Override public Object getThis() { return this; }
}
  1. 配置方法安全表达式处理器:注册自定义表达式根到Spring Security
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() {
        DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
        handler.setExpressionRootFactory(authentication -> new CustomMethodSecurityExpressionRoot(authentication));
        return handler;
    }
}
  1. 端点注解使用:通过@PreAuthorize实现差异化校验逻辑
@RestController
public class SpecialController {
    // 逻辑:非API密钥调用直接放行;API密钥调用必须持有MYROLE
    @PreAuthorize("!isApiKeyCall() or hasRole('MYROLE')")
    @GetMapping("/api/protected-by-apikey-role")
    public ResponseEntity<String> specialEndpoint() {
        return ResponseEntity.ok("Access granted");
    }
}

方案二:自定义过滤器+端点元数据(批量场景高效)

如果有大量端点需要配置相同规则,可通过自定义过滤器批量识别端点标记,自动执行校验。

实现步骤

  1. 自定义端点标记注解:标记需要API密钥角色校验的端点
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiKeyRequiresRole {
    String value() default "MYROLE";
}
  1. 自定义校验过滤器:在认证完成后,检查当前端点是否需要API密钥角色校验
@Component
public class ApiKeyRoleValidationFilter extends OncePerRequestFilter {
    @Autowired
    private HandlerMapping handlerMapping;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        HandlerMethod handlerMethod = getCurrentHandlerMethod(request);
        if (handlerMethod != null) {
            ApiKeyRequiresRole annotation = handlerMethod.getMethodAnnotation(ApiKeyRequiresRole.class);
            if (annotation != null) {
                Authentication auth = SecurityContextHolder.getContext().getAuthentication();
                // 仅对API密钥调用执行角色校验
                if (auth instanceof ApiKeyAuthenticationToken) {
                    String requiredRole = "ROLE_" + annotation.value(); // 匹配Spring Security角色前缀规则
                    boolean hasRequiredRole = auth.getAuthorities().stream()
                            .anyMatch(authz -> authz.getAuthority().equals(requiredRole));
                    if (!hasRequiredRole) {
                        throw new AccessDeniedException("API key caller requires role: " + annotation.value());
                    }
                }
            }
        }
        filterChain.doFilter(request, response);
    }

    private HandlerMethod getCurrentHandlerMethod(HttpServletRequest request) {
        Object handler = handlerMapping.getHandler(request).getHandler();
        return handler instanceof HandlerMethod ? (HandlerMethod) handler : null;
    }
}
  1. 注册过滤器到Security链:确保在API密钥认证过滤器之后执行
@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Autowired
    private ApiKeyRoleValidationFilter apiKeyRoleFilter;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
            // 替换为你实际的API密钥认证过滤器类
            .addFilterAfter(apiKeyRoleFilter, ApiKeyAuthenticationFilter.class);
        return http.build();
    }
}
  1. 端点使用:只需添加自定义注解即可
@RestController
public class BatchController {
    @ApiKeyRequiresRole("MYROLE")
    @GetMapping("/api/batch-endpoint-1")
    public ResponseEntity<String> endpoint1() { return ResponseEntity.ok("OK"); }

    @ApiKeyRequiresRole("MYROLE")
    @GetMapping("/api/batch-endpoint-2")
    public ResponseEntity<String> endpoint2() { return ResponseEntity.ok("OK"); }
}

方案三:修改@Secured生效逻辑(不推荐,灵活性差)

自定义AccessDecisionVoter,修改原生@Secured的投票规则:当调用者是密码登录用户时,忽略MYROLE的校验。但此方案会影响所有使用@Secured("MYROLE")的端点,无法精准控制单个端点,仅适合全局统一规则的场景。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 21:06:24