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

Spring Security 5/OAuth2中JdbcTokenStore的替代方案咨询

兼容Spring Security OAuth2旧JdbcTokenStore与oauth_access_token表的方案

Spring Security 5.x+的OAuth2模块已完成重构,原TokenStore接口被移除,转而基于OAuth2TokenValidator与AuthenticationProvider体系工作。要兼容旧系统的JdbcTokenStore与oauth_access_token表,核心是自定义逻辑读取旧表中的Bearer令牌,将其转换为Spring Security认可的认证对象,具体实现步骤如下:

1. 编写旧令牌数据访问逻辑

复用原有oauth_access_token表结构,创建数据访问类直接查询令牌的有效性、过期时间、用户信息等核心数据:

@Repository
public class LegacyJdbcTokenRepository {
    private final JdbcTemplate jdbcTemplate;

    public LegacyJdbcTokenRepository(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    // 查询令牌基本信息
    public Map<String, Object> getTokenDetails(String tokenValue) {
        String sql = "SELECT user_name, client_id, expires, authentication FROM oauth_access_token WHERE token_id = ?";
        return jdbcTemplate.queryForMap(sql, tokenValue);
    }

    // 验证令牌是否有效(未过期)
    public boolean isTokenValid(String tokenValue) {
        String sql = "SELECT COUNT(1) FROM oauth_access_token WHERE token_id = ? AND expires > NOW()";
        Integer count = jdbcTemplate.queryForObject(sql, new Object[]{tokenValue}, Integer.class);
        return count != null && count > 0;
    }
}

2. 自定义AuthenticationProvider处理旧令牌

实现AuthenticationProvider接口,完成旧令牌的验证逻辑,并生成Spring Security可识别的认证对象:

@Component
public class LegacyTokenAuthenticationProvider implements AuthenticationProvider {
    private final LegacyJdbcTokenRepository tokenRepository;

    public LegacyTokenAuthenticationProvider(LegacyJdbcTokenRepository tokenRepository) {
        this.tokenRepository = tokenRepository;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        BearerTokenAuthenticationToken bearerToken = (BearerTokenAuthenticationToken) authentication;
        String tokenValue = bearerToken.getToken();

        // 验证令牌有效性
        if (!tokenRepository.isTokenValid(tokenValue)) {
            throw new BadCredentialsException("Invalid or expired legacy token");
        }

        // 获取令牌关联的用户与客户端信息
        Map<String, Object> tokenDetails = tokenRepository.getTokenDetails(tokenValue);
        String username = (String) tokenDetails.get("user_name");
        String clientId = (String) tokenDetails.get("client_id");

        // 反序列化旧系统存储的Authentication对象(旧系统用JDK序列化)
        OAuth2Authentication oAuth2Auth;
        try (ByteArrayInputStream bis = new ByteArrayInputStream((byte[]) tokenDetails.get("authentication"));
             ObjectInputStream ois = new ObjectInputStream(bis)) {
            oAuth2Auth = (OAuth2Authentication) ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            throw new RuntimeException("Failed to deserialize legacy authentication data", e);
        }

        // 构建用户权限集合
        Collection<GrantedAuthority> authorities = oAuth2Auth.getUserAuthentication().getAuthorities();

        // 返回标准OAuth2认证对象
        return new OAuth2AuthenticationToken(
                new DefaultOAuth2User(authorities, Map.of("username", username), "username"),
                authorities,
                clientId
        );
    }

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

3. 配置SecurityFilterChain注入自定义Provider

修改Spring Security配置,让自定义的认证提供者生效,同时可保留新OAuth2逻辑的兼容性:

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    private final LegacyTokenAuthenticationProvider legacyTokenProvider;

    public SecurityConfig(LegacyTokenAuthenticationProvider legacyTokenProvider) {
        this.legacyTokenProvider = legacyTokenProvider;
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
                .oauth2ResourceServer(oauth2 -> oauth2
                        .bearerTokenResolver(new DefaultBearerTokenResolver())
                        .authenticationManager(authenticationManager())
                );
        return http.build();
    }

    @Bean
    public AuthenticationManager authenticationManager() {
        ProviderManager manager = new ProviderManager(legacyTokenProvider);
        manager.setEraseCredentials(false);
        return manager;
    }
}

关键注意事项

  • 序列化兼容性:旧系统authentication字段采用JDK序列化,需确保项目中保留旧版本OAuth2相关类(如OAuth2Authentication、OAuth2Request)的包路径与序列化版本一致,避免反序列化失败。
  • 过渡兼容:若需同时支持旧令牌与新JWT令牌,可在BearerTokenResolver中判断令牌格式(如JWT含.分隔符),路由至不同的认证逻辑。
  • 刷新令牌处理:若旧系统需支持令牌刷新,需同理实现oauth_refresh_token表的查询与刷新逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 06:12:37