Spring Cloud Gateway动态注入OAuth2配置到Spring Security上下文的实现方法
Spring Boot Security 运行时动态添加OAuth2身份提供商配置方案
默认的InMemoryReactiveClientRegistrationRepository没有提供公开的动态修改方法,且ClientsConfiguredCondition仅在Spring上下文启动阶段生效,运行时修改spring.security.oauth2.client.registration环境变量不会触发配置更新,可通过以下方案实现动态注入:
第一步:实现支持动态操作的客户端注册仓库
自定义线程安全的ReactiveClientRegistrationRepository实现类,支持运行时增删客户端配置:
import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; import reactor.core.publisher.Mono; import java.util.concurrent.ConcurrentHashMap; public class DynamicReactiveClientRegistrationRepository implements ReactiveClientRegistrationRepository { private final ConcurrentHashMap<String, ClientRegistration> registrations = new ConcurrentHashMap<>(); @Override public Mono<ClientRegistration> findByRegistrationId(String registrationId) { return Mono.justOrEmpty(registrations.get(registrationId)); } // 新增/覆盖客户端配置 public void addClientRegistration(ClientRegistration registration) { registrations.put(registration.getRegistrationId(), registration); } // 可选:删除指定客户端配置 public void removeClientRegistration(String registrationId) { registrations.remove(registrationId); } }
如果是Servlet非响应式环境,将ReactiveClientRegistrationRepository替换为ClientRegistrationRepository,返回值去掉Mono直接返回ClientRegistration即可,逻辑完全一致
第二步:注册自定义仓库为Bean
替换默认的静态实现,可在初始化时添加原有静态配置:
@Bean public ReactiveClientRegistrationRepository clientRegistrationRepository() { DynamicReactiveClientRegistrationRepository repository = new DynamicReactiveClientRegistrationRepository(); // 初始化时添加原有静态客户端,比如示例中的Google客户端 repository.addClientRegistration(googleClientRegistration()); return repository; } // 原有静态客户端构造方法保留即可 private ClientRegistration googleClientRegistration() { return ClientRegistration.withRegistrationId("google") .clientId("google-client-id") .clientSecret("google-client-secret") .clientAuthenticationMethod(ClientAuthenticationMethod.BASIC) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .redirectUriTemplate("{baseUrl}/login/oauth2/code/{registrationId}") .scope("openid", "profile", "email", "address", "phone") .authorizationUri("https://accounts.google.com/o/oauth2/v2/auth") .tokenUri("https://www.googleapis.com/oauth2/v4/token") .userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo") .userNameAttributeName(IdTokenClaimNames.SUB) .jwkSetUri("https://www.googleapis.com/oauth2/v3/certs") .clientName("Google") .build(); }
第三步:运行时动态添加配置
在需要动态新增客户端的业务逻辑中注入自定义仓库,构造ClientRegistration后调用新增方法即可:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class OAuth2ClientService { @Autowired private DynamicReactiveClientRegistrationRepository clientRegistrationRepository; @Autowired private KeycloakProperties keycloak; // 你自己的Keycloak配置类 public void addKeycloakRealmClient(String realmName) { ClientRegistration clientRegistration = ClientRegistration .withRegistrationId(realmName) .clientId(keycloak.getClientGlbId()) .clientSecret(keycloak.getClientGlbSecret()) .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .redirectUri(keycloak.getClientGlbRedirectUri()) .issuerUri(keycloak.getClientGlbIssuerUri().concat(realmName)) .authorizationUri(keycloak.getHost().concat("realms/").concat(realmName).concat("/protocol/openid-connect/auth")) .userInfoUri(keycloak.getHost().concat("realms/").concat(realmName).concat("/protocol/openid-connect/userinfo")) .tokenUri(keycloak.getHost().concat("realms/").concat(realmName).concat("/protocol/openid-connect/token")) // 必须补充jwkSetUri,否则令牌校验会报错 .jwkSetUri(keycloak.getHost().concat("realms/").concat(realmName).concat("/protocol/openid-connect/certs")) .build(); // 添加后配置立即生效,无需重启上下文 clientRegistrationRepository.addClientRegistration(clientRegistration); } }
内容的提问来源于stack exchange,提问作者George Sand
相关产品推荐
相关产品推荐

