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

如何配置Spring同时支持Cognito JWT与自定义API令牌授权?

同时支持Cognito JWT与自定义API令牌的Spring Security配置

核心思路

Spring Security支持配置多套认证逻辑,你可以在保留现有Cognito JWT认证流程的基础上,新增自定义API令牌的认证机制,两者互不干扰,请求会自动匹配有效的认证方式。

步骤1:保留原有Cognito JWT配置

继续维持你已实现的Cognito JWT认证逻辑(比如基于spring-security-oauth2-resource-server的JWT解码器配置),这部分无需修改,确保终端用户的OAuth认证流程正常运行。

步骤2:实现自定义API令牌的认证逻辑

2.1 定义令牌存储模型

先创建实体类存储自定义API令牌的核心信息:

@Entity
public class ApiToken {
    @Id
    private String token;
    private String consumerId; // 标识令牌所属的外部消费者
    private Instant expiresAt;
    private Set<String> authorities; // 令牌拥有的权限集合

    // getter、setter、构造方法
}

对应的数据库操作Repository:

public interface ApiTokenRepository extends JpaRepository<ApiToken, String> {
    Optional<ApiToken> findByTokenAndExpiresAtAfter(String token, Instant now);
}

2.2 自定义AuthenticationToken

创建自定义认证令牌类,封装令牌的认证状态和信息:

public class ApiKeyAuthenticationToken extends AbstractAuthenticationToken {
    private final String token;
    private final Object principal;

    // 未认证状态的构造方法
    public ApiKeyAuthenticationToken(String token) {
        super(null);
        this.token = token;
        this.principal = null;
        setAuthenticated(false);
    }

    // 已认证状态的构造方法
    public ApiKeyAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.token = null;
        this.principal = principal;
        setAuthenticated(true);
    }

    @Override
    public Object getCredentials() {
        return token;
    }

    @Override
    public Object getPrincipal() {
        return principal;
    }
}

2.3 自定义AuthenticationProvider

实现认证提供者,负责验证自定义令牌的有效性:

@Component
public class ApiKeyAuthenticationProvider implements AuthenticationProvider {

    private final ApiTokenRepository tokenRepository;

    public ApiKeyAuthenticationProvider(ApiTokenRepository tokenRepository) {
        this.tokenRepository = tokenRepository;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String token = (String) authentication.getCredentials();
        ApiToken apiToken = tokenRepository.findByTokenAndExpiresAtAfter(token, Instant.now())
                .orElseThrow(() -> new BadCredentialsException("无效或已过期的API令牌"));

        // 转换权限为Spring Security标准格式
        Collection<GrantedAuthority> authorities = apiToken.getAuthorities().stream()
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());

        return new ApiKeyAuthenticationToken(apiToken.getConsumerId(), authorities);
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return ApiKeyAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

2.4 自定义认证过滤器

创建过滤器,从请求头中提取自定义令牌并触发认证流程:

public class ApiKeyAuthenticationFilter extends OncePerRequestFilter {

    private static final String API_TOKEN_HEADER = "X-API-Token";
    private final AuthenticationManager authenticationManager;

    public ApiKeyAuthenticationFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String token = request.getHeader(API_TOKEN_HEADER);
        if (token != null && !token.isBlank()) {
            Authentication authRequest = new ApiKeyAuthenticationToken(token);
            Authentication authResult = authenticationManager.authenticate(authRequest);
            SecurityContextHolder.getContext().setAuthentication(authResult);
        }
        filterChain.doFilter(request, response);
    }
}

步骤3:整合两种认证方式到Spring Security

修改Security配置类,添加自定义过滤器和认证提供者,同时保留Cognito的JWT配置:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private final ApiKeyAuthenticationProvider apiKeyAuthenticationProvider;
    private final JwtDecoder jwtDecoder; // 原有Cognito JWT解码器

    public SecurityConfig(ApiKeyAuthenticationProvider apiKeyAuthenticationProvider, JwtDecoder jwtDecoder) {
        this.apiKeyAuthenticationProvider = apiKeyAuthenticationProvider;
        this.jwtDecoder = jwtDecoder;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(auth -> auth
                        .anyRequest().authenticated()
                )
                // 保留原有Cognito JWT认证配置
                .oauth2ResourceServer(oauth2 -> oauth2
                        .jwt(jwt -> jwt.decoder(jwtDecoder))
                )
                // 添加自定义API令牌过滤器,放在JWT过滤器之前优先检查
                .addFilterBefore(apiKeyAuthenticationFilter(), BearerTokenAuthenticationFilter.class);

        return http.build();
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
        ProviderManager manager = (ProviderManager) config.getAuthenticationManager();
        // 注册自定义认证提供者
        manager.getProviders().add(apiKeyAuthenticationProvider);
        return manager;
    }

    @Bean
    public ApiKeyAuthenticationFilter apiKeyAuthenticationFilter() throws Exception {
        return new ApiKeyAuthenticationFilter(authenticationManager(null));
    }
}

步骤4:实现令牌的颁发与撤销接口

创建管理员专用接口,手动生成和删除API令牌:

@RestController
@RequestMapping("/api/token")
public class ApiTokenController {

    private final ApiTokenRepository tokenRepository;
    private final SecureRandom random = new SecureRandom();

    public ApiTokenController(ApiTokenRepository tokenRepository) {
        this.tokenRepository = tokenRepository;
    }

    // 颁发令牌,仅管理员可调用
    @PostMapping("/issue")
    @PreAuthorize("hasRole('ADMIN')")
    public ApiToken issueToken(@RequestBody TokenRequest request) {
        // 生成32位随机令牌字符串
        byte[] tokenBytes = new byte[16];
        random.nextBytes(tokenBytes);
        String token = Base64.getUrlEncoder().withoutPadding().encodeToString(tokenBytes);

        ApiToken apiToken = new ApiToken();
        apiToken.setToken(token);
        apiToken.setConsumerId(request.getConsumerId());
        apiToken.setExpiresAt(Instant.now().plus(Duration.ofDays(request.getExpireDays())));
        apiToken.setAuthorities(request.getAuthorities());
        return tokenRepository.save(apiToken);
    }

    // 撤销令牌,仅管理员可调用
    @DeleteMapping("/revoke/{token}")
    @PreAuthorize("hasRole('ADMIN')")
    public void revokeToken(@PathVariable String token) {
        tokenRepository.deleteById(token);
    }

    public static class TokenRequest {
        private String consumerId;
        private int expireDays;
        private Set<String> authorities;

        // getter、setter
    }
}

注意事项

  • 过滤器顺序:如果希望优先使用自定义API令牌,就把自定义过滤器放在BearerTokenAuthenticationFilter之前;反之则调整顺序。
  • 令牌存储:生产环境建议用Redis存储令牌,提升查询性能,同时利用Redis的过期自动清理功能。
  • 权限管控:颁发/撤销令牌的接口必须严格限制权限,仅允许管理员操作。
  • 令牌安全:生成令牌时使用足够随机的算法,传输过程强制使用HTTPS。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 03:27:34