Spring Boot 3.1.0下OAuth2授权服务器TokenStore替代方案咨询
方案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
相关产品推荐
相关产品推荐

