我正在进行一个项目,其中要求将单租户应用程序转换为多租户应用程序。如何使用带有spring安全oauth2的密钥斗篷实现对Gateway的多租户支持?任何参考资料请分享
使用ReactiveClientRegistrationRepository在运行时动态注册客户端。然后,您可以将此实现插入到自定义Webfilter中,该过滤器将使用您的自定义存储库根据登录用户的领域(可以是主机名、电子邮件等(计算客户端详细信息
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import reactor.core.publisher.Mono;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrations;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.stereotype.Component;
@Component
public class TenantClientRegistrationRepository implements ReactiveClientRegistrationRepository {
private final Map<String, String> tenants = new HashMap<>();
private final Map<String, Mono<ClientRegistration>> clients = new HashMap<>();
public TenantClientRegistrationRepository() {
this.tenants.put("tenant1", "http://localhost:8080/realms/tenant1");
this.tenants.put("tenant2", "http://localhost:8080/realms/tenant2");
}
@Override
public Mono<ClientRegistration> findByRegistrationId(String registrationId) {
return this.clients.computeIfAbsent(registrationId, this::fromTenant);
}
private Mono<ClientRegistration> fromTenant(String registrationId) {
return Optional.ofNullable(this.tenants.get(registrationId))
.map(uri -> Mono.defer(() -> clientRegistration(uri, registrationId)).cache())
.orElse(Mono.error(new IllegalArgumentException("unknown tenant")));
}
private Mono<ClientRegistration> clientRegistration(String uri, String registrationId) {
return Mono.just(ClientRegistrations.fromIssuerLocation(uri)
.registrationId(registrationId)
.clientId("web-client")//fetch client creds via rest or some other means
.clientSecret("********")
.scope("openid")
.build());
}
@KafkaListener(topics="tenants")
//TODO: Here we will populate tenants(realm) and respective client details based on tenant creation event
public void action(Map<String, Map<String, Object>> action) {
if (action.containsKey("created")) {
Map<String, Object> tenant = action.get("created");
String alias = (String) tenant.get("alias");
String issuerUri = (String) tenant.get("issuerUri");
this.tenants.put(alias, issuerUri);
this.clients.remove(alias);
}
}
}
import static org.springframework.security.config.Customizer.withDefaults;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.oauth2.client.oidc.web.server.logout.OidcClientInitiatedServerLogoutSuccessHandler;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
import org.springframework.security.web.server.WebFilterChainProxy;
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationEntryPoint;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;
import reactor.util.context.Context;
public class TenantFilterChain implements WebFilter, ApplicationContextAware {
private final Map<String, Mono<WebFilter>> tenants = new HashMap<>();
private final ReactiveClientRegistrationRepository clients;
private ApplicationContext context;
public TenantFilterChain(ReactiveClientRegistrationRepository clients) {
this.clients = clients;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
Mono<ClientRegistration> tenant = toTenant(exchange);
Mono<WebFilter> filter = tenant.flatMap(this::fromTenant);
return Mono.zip(tenant, filter)
.flatMap(tuple -> tuple.getT2().filter(exchange, chain)
.contextWrite(Context.of(ClientRegistration.class, tuple.getT1()))
.thenReturn(exchange))
.switchIfEmpty(chain.filter(exchange).thenReturn(exchange))
.then(Mono.empty());
}
private Mono<ClientRegistration> toTenant(ServerWebExchange exchange) {
String host = UriComponentsBuilder.fromUri(exchange.getRequest().getURI())
.build().getHost();
return this.clients.findByRegistrationId(host);
}
private Mono<WebFilter> fromTenant(ClientRegistration registration) {
return this.tenants.computeIfAbsent(registration.getRegistrationId(), tenant -> {
ServerHttpSecurity http = new ContextAwareServerHttpSecurity(this.context);
OidcClientInitiatedServerLogoutSuccessHandler handler =
new OidcClientInitiatedServerLogoutSuccessHandler(this.clients);
handler.setPostLogoutRedirectUri("http://localhost:8282");
ServerAuthenticationEntryPoint entryPoint =
new RedirectServerAuthenticationEntryPoint("/oauth2/authorization/" + tenant);
// @formatter:off
http
.authorizeExchange(e -> e
.pathMatchers("/jwks").permitAll()
.anyExchange().authenticated())
.logout(l -> l.logoutSuccessHandler(handler))
.oauth2Login(withDefaults())
.exceptionHandling(e -> e.authenticationEntryPoint(entryPoint));
// @formatter:on
return Mono.just((WebFilter) new WebFilterChainProxy(http.build())).cache();
});
}
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
this.context = context;
}
private static class ContextAwareServerHttpSecurity extends ServerHttpSecurity {
ContextAwareServerHttpSecurity(ApplicationContext context) {
super.setApplicationContext(context);
}
}
}
参考码基
Josh Cummings关于春季安全OAuth2 的多租户谈话
希望这能有所帮助!!:(