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

Spring Security OAuth2令牌自省返回active:false问题排查

问题:JWT解码显示激活,但自省端点返回active:false

从Spring Security授权服务器获取新的JWT访问令牌后,解码令牌确认其处于激活状态,但调用Spring Security的/oauth2/introspect端点时,始终返回active:false。

相关日志:

logs:
2024-11-04T23:27:27.225+01:00 TRACE 26360 --- [nio-9090-exec-1] TokenIntrospectionAuthenticationProvider : Retrieved authorization with token
2024-11-04T23:27:27.225+01:00 TRACE 26360 --- [nio-9090-exec-1] TokenIntrospectionAuthenticationProvider : Did not introspect token since not active

期望自省端点返回active:true,当前授权服务器配置代码如下:

private static final String[] WHITELIST_URLS = {
 "/register", "/demo", "/authInfo", "/items"
};

@Bean
@Order(1)
public SecurityFilterChain asSecurityFilterChain(HttpSecurity http) throws Exception {
  http
    .cors((cors) -> cors
      .configurationSource(corsConfigurationSource())
    );

  http.authorizeHttpRequests(authorize -> authorize
      .requestMatchers(WHITELIST_URLS).permitAll()
  );

  OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
  http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
    .oidc(Customizer.withDefaults());

  http.exceptionHandling(
    e ->
      e.authenticationEntryPoint(
        new LoginUrlAuthenticationEntryPoint("/login")
      )
  );

  http
          .oauth2ResourceServer((resourceServer) -> resourceServer
    .jwt(Customizer.withDefaults()));

  http
    .logout((logout) -> logout.logoutSuccessHandler(new CustomLogoutSuccessHandler()));

  return http
    .build();
}

@Bean
@Order(2)
public SecurityFilterChain appSecurityFilterChain(HttpSecurity http) throws Exception {
  http.csrf(csrf -> csrf.ignoringRequestMatchers("/logout", "/register", "/resetPassword", "/savePassword", "/changePassword"));
  http
    .cors((cors) -> cors
      .configurationSource(corsConfigurationSource())
    );

  http
    .httpBasic(withDefaults())
    .formLogin(withDefaults())
    .authorizeHttpRequests(authorize -> authorize
      .requestMatchers(WHITELIST_URLS).permitAll()
      .anyRequest().authenticated()
    );

  return http.build();
}

@Bean
AuthorizationServerSettings authorizationServerSettings () {
  return AuthorizationServerSettings.builder().build();
}

问题根源分析

  1. 令牌状态存储缺失:Spring Security授权服务器需要存储颁发的令牌及状态,才能在自省端点验证令牌是否活跃。当前配置未添加令牌存储相关Bean,导致服务器无法跟踪令牌状态,直接返回active:false。
  2. 过滤器链配置冲突:在asSecurityFilterChain中,自定义的authorizeHttpRequests可能覆盖了授权服务器默认的端点权限配置,干扰了自省端点的正常逻辑处理。

修复方案

1. 添加令牌存储及JWT相关Bean

授权服务器需要依赖OAuth2AuthorizationService管理令牌状态,同时配置JWT编解码组件以支持令牌处理:

@Bean
public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
    return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
}

@Bean
public OAuth2TokenGenerator<?> tokenGenerator(JwtEncoder jwtEncoder) {
    JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder);
    OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
    accessTokenGenerator.setJwtGenerator(jwtGenerator);
    OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
    return new DelegatingOAuth2TokenGenerator(accessTokenGenerator, refreshTokenGenerator);
}

@Bean
public JwtEncoder jwtEncoder(KeyPair keyPair) {
    RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
    RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
    RSAKey rsaKey = new RSAKey.Builder(publicKey).privateKey(privateKey).build();
    JWKSource<SecurityContext> jwkSource = new ImmutableJWKSet<>(new JWKSet(rsaKey));
    return new NimbusJwtEncoder(jwkSource);
}

@Bean
public JwtDecoder jwtDecoder(KeyPair keyPair) {
    return NimbusJwtDecoder.withPublicKey((RSAPublicKey) keyPair.getPublic()).build();
}

@Bean
public KeyPair keyPair() {
    try {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        keyPairGenerator.initialize(2048);
        return keyPairGenerator.generateKeyPair();
    } catch (NoSuchAlgorithmException ex) {
        throw new IllegalStateException(ex);
    }
}

2. 修正授权服务器过滤器链配置

移除可能覆盖默认端点权限的自定义规则,确保授权服务器默认配置生效:

@Bean
@Order(1)
public SecurityFilterChain asSecurityFilterChain(HttpSecurity http) throws Exception {
    http
        .cors(cors -> cors.configurationSource(corsConfigurationSource()))
        .exceptionHandling(e -> e.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")))
        .oauth2ResourceServer(resourceServer -> resourceServer.jwt(Customizer.withDefaults()))
        .logout(logout -> logout.logoutSuccessHandler(new CustomLogoutSuccessHandler()));

    // 应用授权服务器默认安全配置,自动处理端点权限
    OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
    http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
        .oidc(Customizer.withDefaults());

    return http.build();
}

3. 确保客户端拥有自省权限

调用自省端点的客户端需注册introspect权限,示例配置如下:

RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
    .clientId("your-client-id")
    .clientSecret("{noop}your-client-secret")
    .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
    .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
    .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
    .redirectUri("http://localhost:8080/login/oauth2/code/your-client")
    .scope(OidcScopes.OPENID)
    .scope(OidcScopes.PROFILE)
    .scope("introspect") // 添加自省权限
    .build();

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 11:22:07