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

Spring Boot 3.1.0下OAuth2授权服务器TokenStore替代方案咨询

替代JwtTokenStore的最优方案(Spring Authorization Server)

方案1:基于OAuth2TokenRepository扩展实现缓存与令牌全生命周期管理

新版授权服务中,OAuth2AuthorizationService是负责令牌持久化、查询、删除的核心接口,默认实现为内存或JDBC版本。你可以通过自定义该接口的实现类,集成缓存(如Redis),直接覆盖令牌缓存、存在性预检查、撤销的需求:

  • 令牌缓存:保存令牌时,将OAuth2Authorization对象存入缓存,键用令牌值,过期时间设为比令牌有效期短1分钟(避免缓存过期后令牌仍有效)
  • 预检查存在性:查询令牌时优先从缓存获取,缓存未命中再查底层存储,同时回写缓存
  • 令牌撤销:删除令牌时同步清除缓存中对应条目

示例代码片段:

@Service
public class CachedOAuth2AuthorizationService implements OAuth2AuthorizationService {

    private final OAuth2AuthorizationService delegate;
    private final RedisTemplate<String, Object> redisTemplate;
    private static final String TOKEN_CACHE_KEY_PREFIX = "oauth2:token:";

    public CachedOAuth2AuthorizationService(OAuth2AuthorizationService delegate, RedisTemplate<String, Object> redisTemplate) {
        this.delegate = delegate;
        this.redisTemplate = redisTemplate;
    }

    @Override
    public void save(OAuth2Authorization authorization) {
        delegate.save(authorization);
        // 缓存所有有效令牌
        authorization.getTokens().values().forEach(token -> {
            if (token.isActive()) {
                String cacheKey = TOKEN_CACHE_KEY_PREFIX + token.getTokenValue();
                redisTemplate.opsForValue().set(cacheKey, authorization, 
                    Duration.between(Instant.now(), token.getExpiresAt()).minusSeconds(60));
            }
        });
    }

    @Override
    public void remove(OAuth2Authorization authorization) {
        delegate.remove(authorization);
        // 清除对应缓存条目
        authorization.getTokens().values().forEach(token -> {
            String cacheKey = TOKEN_CACHE_KEY_PREFIX + token.getTokenValue();
            redisTemplate.delete(cacheKey);
        });
    }

    @Override
    public OAuth2Authorization findById(String id) {
        return delegate.findById(id);
    }

    @Override
    public OAuth2Authorization findByToken(String tokenValue, OAuth2TokenType tokenType) {
        // 优先查缓存
        String cacheKey = TOKEN_CACHE_KEY_PREFIX + tokenValue;
        OAuth2Authorization cachedAuth = (OAuth2Authorization) redisTemplate.opsForValue().get(cacheKey);
        if (cachedAuth != null) {
            return cachedAuth;
        }
        // 缓存未命中则查底层存储并回写缓存
        OAuth2Authorization auth = delegate.findByToken(tokenValue, tokenType);
        if (auth != null) {
            redisTemplate.opsForValue().set(cacheKey, auth, 
                Duration.between(Instant.now(), auth.getAccessToken().getExpiresAt()).minusSeconds(60));
        }
        return auth;
    }
}

替换默认服务配置:

@Configuration
public class AuthorizationServerConfig {

    @Bean
    public OAuth2AuthorizationService oAuth2AuthorizationService(JdbcTemplate jdbcTemplate, RedisTemplate<String, Object> redisTemplate) {
        JdbcOAuth2AuthorizationService delegate = new JdbcOAuth2AuthorizationService(jdbcTemplate, new OAuth2AuthorizationRowMapper());
        return new CachedOAuth2AuthorizationService(delegate, redisTemplate);
    }
}

该方案是官方推荐的扩展方式,完全匹配你需要的三个核心功能,比OAuth2TokenCustomizer更直接(后者仅用于定制令牌内容,不负责存储与生命周期管理)。

方案2:结合OAuth2TokenValidator实现令牌快速有效性检查

如果需要在令牌验证阶段提前拦截已撤销的令牌,可以自定义OAuth2TokenValidator配合缓存使用,避免每次都查询底层存储:

public class RevokedTokenValidator implements OAuth2TokenValidator<OAuth2Token> {

    private final OAuth2TokenValidator<OAuth2Token> delegate;
    private final RedisTemplate<String, Object> redisTemplate;
    private static final String REVOKED_TOKEN_KEY_PREFIX = "oauth2:revoked:";

    public RevokedTokenValidator(OAuth2TokenValidator<OAuth2Token> delegate, RedisTemplate<String, Object> redisTemplate) {
        this.delegate = delegate;
        this.redisTemplate = redisTemplate;
    }

    @Override
    public OAuth2TokenValidatorResult validate(OAuth2Token token) {
        // 先检查令牌是否在撤销缓存中
        String revokedKey = REVOKED_TOKEN_KEY_PREFIX + token.getTokenValue();
        if (Boolean.TRUE.equals(redisTemplate.hasKey(revokedKey))) {
            return OAuth2TokenValidatorResult.failure(
                new OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN, "Token has been revoked", null)
            );
        }
        // 执行默认验证逻辑(签名、过期时间等)
        return delegate.validate(token);
    }
}

配置到授权服务的令牌验证链:

@Bean
public OAuth2TokenValidator<OAuth2Token> tokenValidator(RedisTemplate<String, Object> redisTemplate, JWKSource<SecurityContext> jwkSource) {
    // 默认验证器:检查时间戳、签名
    OAuth2TokenValidator<OAuth2Token> defaultValidator = new DelegatingOAuth2TokenValidator<>(
            new OAuth2TokenTimestampValidator(),
            new OAuth2TokenSignatureValidator(jwkSource)
    );
    return new RevokedTokenValidator(defaultValidator, redisTemplate);
}

@Bean
public OAuth2AuthorizationServerConfigurer authorizationServerConfigurer(OAuth2TokenValidator<OAuth2Token> tokenValidator) {
    return new OAuth2AuthorizationServerConfigurer()
            .tokenEndpoint(tokenEndpoint -> tokenEndpoint
                    .tokenValidator(tokenValidator)
            );
}

该方案可与方案1配合,进一步提升令牌检查的性能。

方案3:用Spring Cache注解简化缓存逻辑

若不想完全自定义OAuth2AuthorizationService,可以借助Spring Cache注解快速给默认实现添加缓存能力:

首先启用缓存配置:

@EnableCaching
@Configuration
public class CacheConfig {
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(59))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
        return RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(config)
                .build();
    }
}

包装默认服务并添加缓存注解:

@Service
@CacheConfig(cacheNames = "oauth2Tokens")
public class CachedOAuth2AuthorizationService implements OAuth2AuthorizationService {

    private final OAuth2AuthorizationService delegate;

    public CachedOAuth2AuthorizationService(OAuth2AuthorizationService delegate) {
        this.delegate = delegate;
    }

    @Override
    @CachePut(key = "#authorization.id")
    public void save(OAuth2Authorization authorization) {
        delegate.save(authorization);
    }

    @Override
    @CacheEvict(key = "#authorization.id")
    public void remove(OAuth2Authorization authorization) {
        delegate.remove(authorization);
        // 清除令牌值对应的缓存
        authorization.getTokens().values().forEach(token -> evictTokenCache(token.getTokenValue()));
    }

    @Override
    @Cacheable(key = "#id")
    public OAuth2Authorization findById(String id) {
        return delegate.findById(id);
    }

    @Override
    @Cacheable(key = "#tokenValue")
    public OAuth2Authorization findByToken(String tokenValue, OAuth2TokenType tokenType) {
        return delegate.findByToken(tokenValue, tokenType);
    }

    @CacheEvict(key = "#tokenValue")
    public void evictTokenCache(String tokenValue) {}
}

该方案代码更简洁,利用Spring Cache的注解减少重复逻辑,同样能实现缓存、预检查和撤销时的缓存清理。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 09:20:19