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

Spring Boot 2 OAuth2 JWT授权服务刷新令牌配置异常排查

问题分析与解决方案

首先,咱们先理清核心问题所在:刷新令牌的处理流程并没有走你配置的AuthenticationManager,而是由Spring Security OAuth2的TokenServices负责,这就是为什么调试时看到了另一个ProviderManager,而且密码登录正常但刷新令牌报错。

为什么会出现这个错误?

当你调用刷新令牌接口时,默认会使用DefaultTokenServices的refreshAccessToken方法。这个方法在加载用户信息时,如果没有明确配置UserDetailsService,会尝试使用PreAuthenticatedAuthenticationProvider,但这个Provider并没有关联你自定义的UserDetailsService,所以抛出了UserDetailsService is required的异常。

另外,你想要实现的「刷新令牌时校验数据库中存储的令牌ID」需求,也不能通过配置AuthenticationProvider来完成——因为刷新令牌的验证逻辑不属于AuthenticationManager的认证流程,而是属于令牌生命周期管理的范畴。

解决方案步骤

1. 自定义TokenServices,加入令牌ID校验逻辑

我们需要继承DefaultTokenServices,重写refreshAccessToken方法,在执行默认刷新逻辑之前,先校验数据库中的令牌ID是否有效。这样既满足你的业务需求,又能解决UserDetailsService缺失的问题。

@Service
public class CustomTokenServices extends DefaultTokenServices {

    private final TokenStore tokenStore;
    private final YourTokenRepository tokenRepository; // 替换成你存储令牌的数据库Repository

    @Autowired
    public CustomTokenServices(TokenStore tokenStore, 
                               UserDetailsService userDetailsService,
                               AuthenticationManager authenticationManager,
                               YourTokenRepository tokenRepository) {
        this.tokenStore = tokenStore;
        this.tokenRepository = tokenRepository;
        
        // 配置TokenServices的核心依赖
        setTokenStore(tokenStore);
        setSupportRefreshToken(true);
        setReuseRefreshToken(false); // 每次刷新生成新的刷新令牌,根据你的需求调整
        setUserDetailsService(userDetailsService); // 关联你的UserDetailsService
        setAuthenticationManager(authenticationManager);
    }

    @Override
    public OAuth2AccessToken refreshAccessToken(String refreshTokenValue, TokenRequest tokenRequest) throws AuthenticationException {
        // 第一步:从TokenStore读取刷新令牌
        OAuth2RefreshToken refreshToken = tokenStore.readRefreshToken(refreshTokenValue);
        if (refreshToken == null) {
            throw new InvalidTokenException("无效的刷新令牌: " + refreshTokenValue);
        }

        // 第二步:校验数据库中的令牌ID是否有效
        String tokenId = refreshToken.getValue(); // 替换成你存储的令牌ID字段
        Optional<YourTokenEntity> storedToken = tokenRepository.findByTokenId(tokenId);
        
        if (!storedToken.isPresent() || !storedToken.get().isValid()) {
            throw new InvalidTokenException("刷新令牌已失效或被撤销: " + refreshTokenValue);
        }

        // 第三步:执行默认的刷新令牌逻辑
        return super.refreshAccessToken(refreshTokenValue, tokenRequest);
    }
}

2. 在授权服务器配置中关联自定义TokenServices

修改AuthorizationServerConfig,将自定义的CustomTokenServices注入到端点配置中,替换默认的TokenServices:

@Configuration @EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    // 省略已有的字段...
    @Autowired
    private CustomTokenServices customTokenServices;

    @Override
    public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(customTokenEnhancer, accessTokenConverter));
        
        endpoints.tokenStore(tokenStore)
                .tokenEnhancer(tokenEnhancerChain)
                .authenticationManager(this.authenticationManagerBean)
                .tokenServices(customTokenServices); // 绑定自定义TokenServices
    }
}

3. 确保UserDetailsService被正确暴露为Bean

在WebSecurityConfig中,将你的UserDetailsService声明为Spring Bean,确保授权服务器能正确注入:

@Configuration @EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    // 替换成你的UserDetailsService实现类
    @Bean
    public UserDetailsService userDetailsService() {
        return new YourCustomUserDetailsService();
    }

    // 省略其他已有配置...
}

关于启动时的异常

你看到的No authenticationProviders and no parentAuthenticationManager defined异常,是因为WebSecurityConfigurerAdapter的内部初始化顺序问题——在configureGlobal执行前,ProviderManager暂时没有配置Provider,但后续配置会生效,所以密码登录功能正常。这个异常可以忽略,或者你可以通过提前初始化Provider来避免,但不会影响功能。

额外说明

  • 如果你希望实现「同一用户仅能在一台设备登录」,除了校验刷新令牌ID,还需要在用户登录成功后,清除该用户之前的所有有效令牌,这样旧的刷新令牌就会失效。
  • setReuseRefreshToken(false)会让每次刷新都生成新的刷新令牌,更符合「单设备登录」的需求,旧的刷新令牌会被标记为无效。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 09:26:30