
在使用spring security构建oauth2资源服务器时,如果采用不透明令牌(opaque token)模式,资源服务器需要通过内省(introspection)机制向认证服务器验证接收到的令牌。这意味着每次请求到达资源服务器时,都会触发一次对认证服务器的内省调用。当认证服务器不稳定或响应缓慢时,这可能导致资源服务器性能下降,甚至因内省失败而频繁返回401未授权错误,严重影响用户体验。为了解决这一问题,引入缓存机制来存储已验证的令牌内省结果变得至关重要。
Spring Security 5.x 提供了对OAuth2资源服务器的支持,其中不透明令牌的内省通过OpaqueTokenIntrospector接口实现。默认情况下,Spring Security会使用NimbusOpaqueTokenIntrospector,它直接通过HTTP请求与认证服务器的内省端点进行通信。
标准的Spring Security配置如下:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Value("${spring.security.oauth2.resourceserver.opaque-token.client-id}")
private String clientId;
@Value("${spring.security.oauth2.resourceserver.opaque-token.client-secret}")
private String clientSecret;
@Value("${spring.security.oauth2.resourceserver.opaque-token.introspection-uri}")
private String introspectionUri;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests ->
authorizeRequests
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2ResourceServer ->
oauth2ResourceServer
.opaqueToken(opaqueToken ->
opaqueToken
.introspectionClientCredentials(this.clientId, this.clientSecret)
.introspectionUri(this.introspectionUri)
)
);
}
}要引入缓存,我们需要替换或包装默认的OpaqueTokenIntrospector实现。
本节将展示如何创建一个自定义的OpaqueTokenIntrospector,利用Ehcache作为缓存解决方案,以存储和管理已内省的令牌。
首先,创建一个名为CustomOpaqueTokenIntrospector的类,它将实现OpaqueTokenIntrospector接口,并封装一个底层的NimbusOpaqueTokenIntrospector实例。
import org.ehcache.Cache;
import org.ehcache.CacheManager;
import org.ehcache.config.builders.CacheConfigurationBuilder;
import org.ehcache.config.builders.CacheManagerBuilder;
import org.ehcache.config.builders.ResourcePoolsBuilder;
import org.ehcache.expiry.ExpiryPolicyBuilder;
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
import org.springframework.security.oauth2.server.resource.introspection.NimbusOpaqueTokenIntrospector;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
import java.time.Duration;
import java.time.Instant;
public class CustomOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
private final OpaqueTokenIntrospector delegateIntrospector; // 委托给实际的内省器
private final CacheManager cacheManager;
private final Cache<String, OAuth2AuthenticatedPrincipal> accessTokensCache;
public CustomOpaqueTokenIntrospector(String uri, String clientId, String clientSecret) {
// 1. 初始化Ehcache缓存管理器
this.cacheManager = CacheManagerBuilder.newCacheManagerBuilder().build();
this.cacheManager.init();
// 2. 配置并创建缓存实例
// 缓存名称为"iamCache",键为String(token),值为OAuth2AuthenticatedPrincipal
// 堆内存大小限制为1000个条目,过期策略为TTL(time-to-live)1800秒(30分钟)
this.accessTokensCache = cacheManager.createCache("iamCache",
CacheConfigurationBuilder
.newCacheConfigurationBuilder(String.class, OAuth2AuthenticatedPrincipal.class,
ResourcePoolsBuilder.heap(1000))
.withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofSeconds(1800))));
// 3. 初始化委托的内省器,这里使用NimbusOpaqueTokenIntrospector
this.delegateIntrospector = new NimbusOpaqueTokenIntrospector(uri, clientId, clientSecret);
}
@Override
public OAuth2AuthenticatedPrincipal introspect(String token) {
// 1. 尝试从缓存中获取令牌对应的认证主体
OAuth2AuthenticatedPrincipal principal = accessTokensCache.get(token);
if (principal != null) {
// 2. 如果缓存中存在,检查其过期时间("exp"属性)
// 注意:这里假设OAuth2AuthenticatedPrincipal中包含"exp"属性,
// 且其类型为Instant。实际应用中需根据认证服务器返回的内省结果调整。
Instant expirationTime = principal.getAttribute("exp");
if (expirationTime != null && expirationTime.isAfter(Instant.now())) {
// 3. 如果未过期,直接返回缓存中的认证主体
return principal;
} else {
// 4. 如果已过期,从缓存中移除
accessTokensCache.remove(token);
}
}
// 5. 如果缓存中不存在或已过期,调用委托的内省器进行实际的内省请求
principal = delegateIntrospector.introspect(token);
// 6. 将新的认证主体存入缓存
accessTokensCache.put(token, principal);
return principal;
}
/**
* 提供一个方法用于获取缓存实例,尽管在实际生产代码中可能不需要直接暴露。
* @return 缓存实例
*/
public Cache<String, OAuth2AuthenticatedPrincipal> getAccessTokensCache() {
return accessTokensCache;
}
/**
* 在应用程序关闭时,需要关闭CacheManager以释放资源。
* 可以在Spring的@PreDestroy方法中调用此方法。
*/
public void closeCacheManager() {
if (cacheManager != null && cacheManager.getStatus() == org.ehcache.Status.AVAILABLE) {
cacheManager.close();
}
}
}代码解析:
为了让Spring Security使用我们自定义的CustomOpaqueTokenIntrospector,我们需要在安全配置类中将其暴露为一个@Bean。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
import org.springframework.beans.factory.annotation.Value;
import javax.annotation.PreDestroy; // 引入PreDestroy注解
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Value("${spring.security.oauth2.resourceserver.opaque-token.client-id}")
private String clientId;
@Value("${spring.security.oauth2.resourceserver.opaque-token.client-secret}")
private String clientSecret;
@Value("${spring.security.oauth2.resourceserver.opaque-token.introspection-uri}")
private String introspectionUri;
private CustomOpaqueTokenIntrospector customIntrospector; // 声明实例
@Bean
public OpaqueTokenIntrospector introspector() {
// 创建并返回自定义的OpaqueTokenIntrospector实例
this.customIntrospector = new CustomOpaqueTokenIntrospector(this.introspectionUri, this.clientId, this.clientSecret);
return this.customIntrospector;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests ->
authorizeRequests
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2ResourceServer ->
oauth2ResourceServer
.opaqueToken(opaqueToken ->
// 将自定义的introspector Bean注入到Spring Security配置中
opaqueToken.introspector(introspector())
)
);
}
// 在Bean销毁前关闭Ehcache CacheManager
@PreDestroy
public void destroy() {
if (customIntrospector != null) {
customIntrospector.closeCacheManager();
}
}
}配置解析:
<!-- Maven -->
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.10.8</version> <!-- 使用最新稳定版本 -->
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version> <!-- Java 9+ 可能需要 -->
</dependency>通过自定义OpaqueTokenIntrospector并集成Ehcache,我们成功地为Spring Security OAuth2不透明令牌的内省请求引入了缓存机制。这不仅能有效减轻认证服务器的压力,提高资源服务器的性能和稳定性,还能在认证服务器暂时不可用时提供一定的容错能力。在实际部署时,务必根据应用场景和性能需求,合理配置缓存策略,并考虑分布式环境下的缓存解决方案。
以上就是Spring Security OAuth2 不透明令牌内省请求缓存实战指南的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号