在React式Spring安全中,有一个关于spring documentation中称为“动态租户”的多租户用例的说明。这解释了如何在身份验证管理器解析器的帮助下动态(即在运行时)添加身份验证管理器,该解析器是一个对象,将查询身份验证管理器的Map(由颁发者字符串键控)或颁发者列表。在后一种情况下,将使用默认的身份验证管理器。可以在配置SecurityWebFilterChain
期间创建解析器,如下所示...
@Bean
public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {
this.addExistingIssuers();
JwtIssuerReactiveAuthenticationManagerResolver authenticationManagerResolver =
new JwtIssuerReactiveAuthenticationManagerResolver(allIssuers);
return http
.securityContextRepository(NoOpServerSecurityContextRepository.getInstance())
.csrf(spec -> spec.disable())
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.authorizeExchange(exchanges -> exchanges
.pathMatchers(HttpMethod.GET, "/actuator/**").permitAll()
.pathMatchers(HttpMethod.POST, "/public/login").permitAll()
.anyExchange().authenticated())
.oauth2ResourceServer(oauth2 -> oauth2
.authenticationManagerResolver(authenticationManagerResolver))
.build();
}
字符串
此方法的第一行添加任何预先存在的发行者(例如对于已经在应用数据库中的租户)。当添加新租户时,可能需要将新的颁发者添加到传递(通过引用)到JwtIssuerReactiveAuthenticationMangerResolver
的颁发者集合中,这可以通过提供这样的方法来实现,该方法可以在新租户创建期间调用:
public void addNewIssuer(Account acct) {
log.debug("Adding issuer manager for account {}", acct.getName());
this.addIssuer(
getIssuerUrl(this.awsRegion, acct.getUserPoolId()),
acct.getAppClientId(),
acct.getUserPoolId());
}
型
这一切都很好,但这里的事情...
想象一下,您正在建立一个新的多租户平台,您的租户可以在其中通过租赁创建自助服务。当然,最初的发行人列表应该是空的(不是null,请注意......只是空的)。当spring服务启动时,它应该运行得很好,拒绝任何包含JWT的调用,因为发布者列表是空的。然后,一旦租户注册,他们的JWT发布者将使用上面的方法添加到列表中,并且需要身份验证的非公开调用将工作-只要它们来自这个新的发布者。
实际发生的是抛出异常(似乎是因为Assert!)。因此服务无法启动。下面是JwtIssuerReactiveAuthenticationManagerResolver的源代码-注意Assert.notEmpty(...)的调用:
/**
* Construct a {@link JwtIssuerReactiveAuthenticationManagerResolver} using the
* provided parameters
* @param trustedIssuers a collection of trusted issuers
*/
public JwtIssuerReactiveAuthenticationManagerResolver(Collection<String> trustedIssuers) {
Assert.notEmpty(trustedIssuers, "trustedIssuers cannot be empty");
this.authenticationManager = new ResolvingAuthenticationManager(
new TrustedIssuerJwtAuthenticationManagerResolver(new ArrayList<>(trustedIssuers)::contains));
}
型
我不明白为什么一个服务不应该在没有受信任的发布者的情况下启动,然后动态地增量获取受信任的发布者。当我(还)没有任何租户时,我应该与哪个“特殊”发行商初始化列表?
我尝试用空列表启动一个服务;我希望它能工作,我可以动态地添加新的发行者作为租户(动态地)添加到服务。
实际发生的情况是服务无法启动:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.security.config.annotation.web.reactive.WebFluxSecurityConfiguration': Unsatisfied dependency expressed through method 'setSecurityWebFilterChains' parameter 0: Error creating bean with name 'securityFilterChain' defined in class path resource [uk/co/govbuddy/account/config/SecurityConfig.class]: Failed to instantiate [org.springframework.security.web.server.SecurityWebFilterChain]: Factory method 'securityFilterChain' threw exception with message: trustedIssuers cannot be empty
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.resolveMethodArguments(AutowiredAnnotationBeanPostProcessor.java:819) ~[spring-beans-6.0.10.jar:6.0.10]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:771) ~[spring-beans-6.0.10.jar:6.0.10]
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:145) ~[spring-beans-6.0.10.jar:6.0.10]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:483) ~[spring-beans-6.0.10.jar:6.0.10]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1416) ~[spring-beans-6.0.10.jar:6.0.10]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:597) ~[spring-beans-6.0.10.jar:6.0.10]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:520) ~[spring-beans-6.0.10.jar:6.0.10]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[spring-beans-6.0.10.jar:6.0.10]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.0.10.jar:6.0.10]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) ~[spring-beans-6.0.10.jar:6.0.10]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[spring-beans-6.0.10.jar:6.0.10]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:973) ~[spring-beans-6.0.10.jar:6.0.10]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:941) ~[spring-context-6.0.10.jar:6.0.10]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:608) ~[spring-context-6.0.10.jar:6.0.10]
at org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext.refresh(ReactiveWebServerApplicationContext.java:66) ~[spring-boot-3.1.1.jar:3.1.1]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:734) ~[spring-boot-3.1.1.jar:3.1.1]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:436) ~[spring-boot-3.1.1.jar:3.1.1]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:312) ~[spring-boot-3.1.1.jar:3.1.1]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306) ~[spring-boot-3.1.1.jar:3.1.1]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1295) ~[spring-boot-3.1.1.jar:3.1.1]
at uk.co.govbuddy.account.AccountApplication.main(AccountApplication.java:17) ~[classes/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:50) ~[spring-boot-devtools-3.1.1.jar:3.1.1]
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'securityFilterChain' defined in class path resource [uk/co/govbuddy/account/config/SecurityConfig.class]: Failed to instantiate [org.springframework.security.web.server.SecurityWebFilterChain]: Factory method 'securityFilterChain' threw exception with message: trustedIssuers cannot be empty
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:659) ~[spring-beans-6.0.10.jar:6.0.10]
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:647) ~[spring-beans-6.0.10.jar:6.0.10]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1332) ~[spring-beans-6.0.10.jar:6.0.10]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1162) ~[spring-beans-6.0.10.jar:6.0.10]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:560) ~[spring-beans-6.0.10.jar:6.0.10]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:520) ~[spring-beans-6.0.10.jar:6.0.10]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[spring-beans-6.0.10.jar:6.0.10]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.0.10.jar:6.0.10]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) ~[spring-beans-6.0.10.jar:6.0.10]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[spring-beans-6.0.10.jar:6.0.10]
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254) ~[spring-beans-6.0.10.jar:6.0.10]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.addCandidateEntry(DefaultListableBeanFactory.java:1633) ~[spring-beans-6.0.10.jar:6.0.10]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1597) ~[spring-beans-6.0.10.jar:6.0.10]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveMultipleBeans(DefaultListableBeanFactory.java:1488) ~[spring-beans-6.0.10.jar:6.0.10]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1375) ~[spring-beans-6.0.10.jar:6.0.10]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1337) ~[spring-beans-6.0.10.jar:6.0.10]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.resolveMethodArguments(AutowiredAnnotationBeanPostProcessor.java:811) ~[spring-beans-6.0.10.jar:6.0.10]
... 25 common frames omitted
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.web.server.SecurityWebFilterChain]: Factory method 'securityFilterChain' threw exception with message: trustedIssuers cannot be empty
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:171) ~[spring-beans-6.0.10.jar:6.0.10]
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:655) ~[spring-beans-6.0.10.jar:6.0.10]
... 41 common frames omitted
Caused by: java.lang.IllegalArgumentException: trustedIssuers cannot be empty
at org.springframework.util.Assert.notEmpty(Assert.java:479) ~[spring-core-6.0.10.jar:6.0.10]
at org.springframework.security.oauth2.server.resource.authentication.JwtIssuerReactiveAuthenticationManagerResolver.<init>(JwtIssuerReactiveAuthenticationManagerResolver.java:83) ~[spring-security-oauth2-resource-server-6.1.1.jar:6.1.1]
at uk.co.govbuddy.account.config.SecurityConfig.securityFilterChain(SecurityConfig.java:67) ~[classes/:na]
at uk.co.govbuddy.account.config.SecurityConfig$$SpringCGLIB$$0.CGLIB$securityFilterChain$1(<generated>) ~[classes/:na]
at uk.co.govbuddy.account.config.SecurityConfig$$SpringCGLIB$$2.invoke(<generated>) ~[classes/:na]
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:258) ~[spring-core-6.0.10.jar:6.0.10]
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331) ~[spring-context-6.0.10.jar:6.0.10]
at uk.co.govbuddy.account.config.SecurityConfig$$SpringCGLIB$$0.securityFilterChain(<generated>) ~[classes/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:139) ~[spring-beans-6.0.10.jar:6.0.10]
... 42 common frames omitted
Process finished with exit code 0
型
2条答案
按热度按时间mv1qrgav1#
好的,我在再次阅读spring docs后得到了这个工作(!),还有一点实验。
总结一下
JwtIssuerReactiveAuthenticationManagerResolver
有多个构造函数以下是安全配置需要具备的内容:
1.身份验证管理器Map的声明:
字符串
型
1.注意上面方法的第一行,它调用了这个方法(这里我从一个自动连接的
accountRepository
中检索租户):型
addAuthenticationManager
方法看起来像这样(沿着一个帮助器方法来获取发行者URL):型
1.最后,当你想在以后的某个时候添加一个新的
AuthenticationManager
时,只需调用这样的方法-在本例中,Account
代表租户,在我的例子中,它包括AWS Cognito用户池:型
希望这可以帮助其他人尝试在响应式Spring安全中使用动态多租户。值得注意的是,对
JwtIssuerReactiveAuthenticationManagerResolver
的构造函数调用与spring docs中显示的略有不同,但上面的版本对我来说是有效的。w7t8yxp52#
只需要定义你自己的
ReactiveAuthenticationManagerResolver<ServerWebExchange>
实现,而没有非空列表限制:从JwtIssuerReactiveAuthenticationManagerResolver
复制或将其用作您自己的代理中的委托(在空的发行者列表上使用保护)。可能是这样的:
字符串
然后,您可以将此身份验证管理器解析程序注入以进行配置:
型
也可以在
@Controller
或@Service
中添加和删除受信任的发行方。型