Spring Boot 3.0升级后Spring Cloud OAuth2迁移至Spring Security求助
Spring Boot Security 3.0 资源服务器迁移方案(替代废弃Spring Cloud OAuth2组件)
核心问题拆解
- 依赖冲突:
spring-cloud-starter-oauth2:2.2.5.RELEASE与Spring Boot 3.x配套的Spring Security 6.x不兼容,导致JwtAuthenticationToken和旧的OAuth2Authentication类型转换失败。 - 组件废弃:
RemoteTokenServices、AccessTokenConverter、OAuth2Authentication等旧Spring Cloud OAuth2组件已被Spring Security OAuth2 Resource Server模块替代。
迁移步骤
1. 清理旧依赖
移除spring-cloud-starter-oauth2依赖,Spring Boot 3.x的spring-boot-starter-oauth2-resource-server已包含资源服务器所有必需功能:
// 删除此行 // implementation 'org.springframework.cloud:spring-cloud-starter-oauth2:2.2.5.RELEASE'
2. 替换自定义Token验证逻辑(替代MyRemoteTokenServices)
旧的RemoteTokenServices用于调用授权服务器的check_token端点验证令牌,在Spring Security 6.x中,改用OAuth2IntrospectionAuthenticationProvider实现令牌 introspection 逻辑,同时保留你的缓存、过期检查和自定义信任配置:
package si.osi.rekonoapi.server.services; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Jwts; import lombok.NonNull; import org.apache.commons.lang3.SerializationUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.http.client.ClientHttpResponse; import org.springframework.security.core.AuthenticationException; import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionAuthenticatedPrincipal; import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionException; import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionService; import org.springframework.web.client.DefaultResponseErrorHandler; import org.springframework.web.client.RestTemplate; import si.osi.rekonoapi.server.utils.Utils; import java.io.IOException; import java.util.Map; public class MyOauth2IntrospectionService implements OAuth2IntrospectionService { private static final Logger LOG = LoggerFactory.getLogger(MyOauth2IntrospectionService.class); private final OAuth2IntrospectionService delegate; private final Cache cache; public MyOauth2IntrospectionService(CacheManager cacheManager, String trustPath, String trustKey, String introspectionUri, String clientId, String clientSecret) { // 初始化自定义RestTemplate(保留原信任配置) RestTemplate restTemplate = Utils.prepareRestClient( "OAuth2IntrospectionService", null, null, trustPath, trustKey); restTemplate.setErrorHandler(new DefaultResponseErrorHandler() { @Override public void handleError(@NonNull ClientHttpResponse response) throws IOException { if (response.getStatusCode().value() != 400) { super.handleError(response); } } }); // 使用默认的Introspection服务实现,传入自定义RestTemplate、端点、客户端信息 this.delegate = new org.springframework.security.oauth2.server.resource.introspection.NimbusOAuth2IntrospectionService( introspectionUri, clientId, clientSecret, restTemplate); this.cache = cacheManager.getCache("accessTokens"); } @Override public OAuth2AuthenticatedPrincipal introspect(String token) throws OAuth2IntrospectionException { // 先检查令牌是否过期 if (isExpired(token)) { cache.evictIfPresent(token); throw new OAuth2IntrospectionException("Token is expired."); } // 从缓存获取验证结果 OAuth2AuthenticatedPrincipal result = cache.get(token, OAuth2AuthenticatedPrincipal.class); if (result == null) { LOG.trace("Authentication info not in cache. Calling introspection endpoint..."); result = delegate.introspect(token); // 缓存时转换为可序列化的实现(默认的Nimbus实现不可序列化) OAuth2IntrospectionAuthenticatedPrincipal serializablePrincipal = new OAuth2IntrospectionAuthenticatedPrincipal(result.getAttributes(), result.getAuthorities()); cache.put(token, serializablePrincipal); } else { LOG.trace("Authentication info present in cache. Reusing..."); } return SerializationUtils.clone((OAuth2IntrospectionAuthenticatedPrincipal) result); } private boolean isExpired(String accessToken) { try { String withoutSignature = accessToken.substring(0, accessToken.lastIndexOf('.') + 1); Jwts.parserBuilder().build().parseClaimsJwt(withoutSignature); } catch (ExpiredJwtException exc) { return true; } return false; } }
3. 重构Security配置类
移除所有废弃组件的Bean,根据你的需求选择JWT本地验证或令牌Introspection模式(二选一,根据授权服务器支持的方式):
方案A:JWT本地验证模式(使用JWKS端点)
适合授权服务器提供JWKS端点,本地验证令牌签名和有效性:
@Configuration @EnableWebSecurity public class SecurityConfig { @Value("${security.oauth2.resource.jwk.key-set-uri}") private String jwkSetUri; private final CacheManager cacheManager; private final MySecurityResourceProperties properties; public SecurityConfig(CacheManager cacheManager, @Qualifier("mySecurityResourceProperties") MySecurityResourceProperties properties) { this.cacheManager = cacheManager; this.properties = properties; } @Bean public JwtDecoder jwtDecoder() { return NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build(); } @Bean @Order(1) public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authorize -> authorize .requestMatchers("some_endpoint").permitAll() .anyRequest().authenticated() ) .oauth2ResourceServer(oauth2 -> oauth2 .jwt(jwt -> jwt .jwtAuthenticationConverter(customJwtAuthenticationConverter()) ) ) .exceptionHandling(exceptions -> exceptions .authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint()) .accessDeniedHandler(new BearerTokenAccessDeniedHandler()) ); return http.build(); } // 替代原JwtConverter,将JWT claims设置为认证信息的details @Bean public JwtAuthenticationConverter customJwtAuthenticationConverter() { JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_"); JwtAuthenticationConverter converter = new JwtAuthenticationConverter(); converter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter); // 自定义转换逻辑,将JWT的所有claims设置为details converter.setPrincipalClaimName("sub"); converter.setAuthenticationConverter(jwt -> { JwtAuthenticationToken token = (JwtAuthenticationToken) converter.getAuthenticationConverter().convert(jwt); if (token != null) { token.setDetails(jwt.getClaims()); } return token; }); return converter; } }
方案B:令牌Introspection模式(调用授权服务器check_token端点)
适合需要授权服务器实时验证令牌状态的场景:
@Configuration @EnableWebSecurity public class SecurityConfig { @Value("${security.oauth2.resource.token-info-trust:}") private String API_TRUST_PATH; @Value("${security.oauth2.resource.token-info-trust-key:}") private String API_TRUST_KEY; private final CacheManager cacheManager; private final MySecurityResourceProperties properties; public SecurityConfig(CacheManager cacheManager, @Qualifier("mySecurityResourceProperties") MySecurityResourceProperties properties) { this.cacheManager = cacheManager; this.properties = properties; } @Bean public OAuth2IntrospectionService introspectionService() { return new MyOauth2IntrospectionService( cacheManager, API_TRUST_PATH, API_TRUST_KEY, properties.getTokenInfoUri(), properties.getClientId(), properties.getClientSecret() ); } @Bean @Order(1) public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authorize -> authorize .requestMatchers("some_endpoint").permitAll() .anyRequest().authenticated() ) .oauth2ResourceServer(oauth2 -> oauth2 .opaqueToken(opaque -> opaque .introspectionService(introspectionService()) ) ) .exceptionHandling(exceptions -> exceptions .authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint()) .accessDeniedHandler(new BearerTokenAccessDeniedHandler()) ); return http.build(); } }
4. 替换业务代码中的类型引用
将所有业务代码中OAuth2Authentication的引用替换为:
- JWT模式:
JwtAuthenticationToken - Introspection模式:
OAuth2IntrospectionAuthenticationToken
例如,获取认证信息的代码:
// 旧代码 OAuth2Authentication auth = (OAuth2Authentication) SecurityContextHolder.getContext().getAuthentication(); // 新代码(JWT模式) JwtAuthenticationToken auth = (JwtAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); Map<String, Object> details = auth.getToken().getClaims(); // 对应原auth.getDetails()
最终依赖配置
确保依赖只保留Spring Boot官方组件:
// spring-boot implementation 'org.springframework.boot:spring-boot-starter-data-jpa:3.0.0' implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server:3.0.0' implementation 'org.springframework.boot:spring-boot-starter-web:3.0.0' implementation 'org.springframework.boot:spring-boot-starter-security:3.0.0' implementation 'org.springframework.boot:spring-boot-starter-cache:3.0.0' testImplementation 'org.springframework.boot:spring-boot-starter-test:3.0.0' testImplementation 'org.springframework.security:spring-security-test:6.3.0'
内容的提问来源于stack exchange,提问作者user3475581
相关产品推荐
相关产品推荐

