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

Spring Boot中验证Cognito OAuth2令牌与传入userId匹配的方法

验证传入userId与Cognito令牌用户一致性的优雅实现

你的Spring Boot应用已经通过OAuth2资源服务器完成了Cognito JWT的验证,要实现传入userId与令牌所属用户的一致性校验,无需调用Cognito的userInfo端点(JWT本身是自包含令牌,已携带用户核心信息,调用外部接口会增加性能开销),以下是两种优雅的实现方式:

方式一:方法层面校验(推荐,灵活可控)

利用Spring Security的方法级安全注解@PreAuthorize,直接在Controller方法中添加校验逻辑,无需额外编写过滤器或注解。

直接使用表达式校验

如果你的Cognito令牌中用户ID存储在sub字段(Cognito默认的用户唯一标识),可以直接在方法上添加:

@GetMapping("/users/{userId}")
@PreAuthorize("#userId == authentication.principal.subject")
public ResponseEntity<User> getUserDetail(@PathVariable String userId) {
    // 业务逻辑
    return ResponseEntity.ok(userService.getById(userId));
}

如果你的用户ID存储在cognito:username字段(自定义用户名场景),则修改表达式为:

@PreAuthorize("#userId == authentication.principal.attributes['cognito:username']")

自定义权限校验器(适合复杂场景)

如果需要复用校验逻辑,或者表达式过于冗长,可以自定义权限校验器:

  1. 实现PermissionEvaluator接口:
public class UserIdMatchPermissionEvaluator implements PermissionEvaluator {
    @Override
    public boolean hasPermission(Authentication authentication, Object targetUserId, Object permission) {
        if (authentication == null || !(authentication.getPrincipal() instanceof Jwt)) {
            return false;
        }
        Jwt jwt = (Jwt) authentication.getPrincipal();
        // 根据实际字段获取令牌中的用户ID,这里用sub示例
        String tokenUserId = jwt.getSubject();
        return tokenUserId.equals(targetUserId.toString());
    }

    @Override
    public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
        return false;
    }
}
  1. 在SecurityConfig中配置方法安全表达式处理器:
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(securedEnabled = true)
public class SecurityConfig {

    // ... 原有的filterChain配置

    @Bean
    public MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
        DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
        handler.setPermissionEvaluator(new UserIdMatchPermissionEvaluator());
        return handler;
    }
}
  1. 在Controller方法中使用:
@GetMapping("/users/{userId}")
@PreAuthorize("hasPermission(#userId, 'USER_ID_MATCH')")
public ResponseEntity<User> getUserDetail(@PathVariable String userId) {
    // 业务逻辑
    return ResponseEntity.ok(userService.getById(userId));
}

方式二:全局过滤器校验(适合批量接口统一处理)

如果你的大部分接口都需要校验userId与令牌的一致性,可以编写全局过滤器,在认证完成后自动校验:

  1. 实现OncePerRequestFilter:
@Component
public class JwtUserIdValidationFilter extends OncePerRequestFilter {

    private final AntPathMatcher pathMatcher = new AntPathMatcher();

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 定义需要校验的接口路径模板,根据你的实际接口规则调整
        String[] protectedPatterns = {"/users/{userId}", "/orders/{userId}/**"};
        
        // 判断当前请求是否匹配需要校验的路径
        boolean needValidation = Arrays.stream(protectedPatterns)
                .anyMatch(pattern -> pathMatcher.match(pattern, request.getRequestURI()));
        
        if (needValidation) {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            if (authentication != null && authentication.getPrincipal() instanceof Jwt jwt) {
                String tokenUserId = jwt.getSubject(); // 或cognito:username
                // 从路径中提取传入的userId
                String pathUserId = extractUserIdFromPath(request, protectedPatterns);
                
                if (!tokenUserId.equals(pathUserId)) {
                    response.sendError(HttpServletResponse.SC_FORBIDDEN, "传入的userId与令牌所属用户不匹配");
                    return;
                }
            }
        }
        filterChain.doFilter(request, response);
    }

    private String extractUserIdFromPath(HttpServletRequest request, String[] patterns) {
        for (String pattern : patterns) {
            Map<String, String> variables = pathMatcher.extractUriTemplateVariables(pattern, request.getRequestURI());
            if (variables.containsKey("userId")) {
                return variables.get("userId");
            }
        }
        return null;
    }
}
  1. 在SecurityConfig中注册过滤器,确保在OAuth2资源服务器过滤器之前执行:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http, JwtUserIdValidationFilter userIdValidationFilter) throws Exception {
    http
            .requiresChannel(channel -> channel.anyRequest().requiresSecure())
            .cors().and().csrf().disable()
            .addFilterBefore(userIdValidationFilter, OAuth2ResourceServerFilter.class) // 添加自定义过滤器
            .authorizeHttpRequests(auth -> auth
                    .requestMatchers(new AntPathRequestMatcher("/actuator/**")).permitAll()
                    .anyRequest().authenticated())
            .oauth2ResourceServer().jwt();
    return http.build();
}

关键说明

  • Cognito JWT默认包含sub(用户唯一UUID)、cognito:username(用户登录名)等字段,可通过jwt.getClaims()查看所有字段,选择与业务匹配的用户ID字段。
  • 优先使用方法层面校验,因为它更灵活,能针对不同接口做差异化处理;全局过滤器适合批量统一规则的场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 19:22:47