实现 OAuth 2.0 资源服务器多租户时遇到错误
Posted
技术标签:
【中文标题】实现 OAuth 2.0 资源服务器多租户时遇到错误【英文标题】:Error encountered implementing OAuth 2.0 Resource Server multi-tenancy 【发布时间】:2021-12-12 02:56:50 【问题描述】:我已经实现了 here 指定的代码,以将多租户功能添加到我的 Spring Security 配置中。但是,当我的 Spring Boot 应用程序启动时,我遇到以下错误:
2021-10-26 | 10:31:37.762 | main | WARN | ConfigServletWebServerApplicationContext | Trace: | Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'springSecurityFilterChain' defined in class path resource [org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [javax.servlet.Filter]: Factory method 'springSecurityFilterChain' threw exception; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.security.oauth2.jwt.JwtDecoder' available
2021-10-26 | 10:31:39.361 | main | ERROR | o.s.b.d.LoggingFailureAnalysisReporter | Trace: |
***************************
APPLICATION FAILED TO START
***************************
Description:
Method springSecurityFilterChain in org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration required a bean of type 'org.springframework.security.oauth2.jwt.JwtDecoder' that could not be found.
Action:
Consider defining a bean of type 'org.springframework.security.oauth2.jwt.JwtDecoder' in your configuration.
文档说明:
这很好,因为发布者端点是延迟加载的。实际上,对应的 JwtAuthenticationProvider 只是在发送了对应 issuer 的第一个请求时才会实例化。
我不认为在应用程序启动时 JwtDecoder
会根据本文档被实例化。我的配置中缺少什么?
更新
在 Steve Riesenberg 的帮助下,我现在正在编译以下代码。你可以在我的代码 sn-p 中看到我曾经工作的内容(即在我们有多租户要求之前)现在被注释掉了:
//.jwt().jwtAuthenticationConverter(jwtAccessTokenConverter);
String[] issuers = new String[] "https://www.example.com/auth/realms/example";
JwtIssuerAuthenticationManagerResolver jwtIssuerAuthenticationManagerResolver =
new JwtIssuerAuthenticationManagerResolver(issuers);
...
.anyRequest()
.authenticated()
.and()
.oauth2ResourceServer(
oauth2ResourceServerConfigurer ->
oauth2ResourceServerConfigurer
.authenticationManagerResolver(jwtIssuerAuthenticationManagerResolver)
.authenticationEntryPoint(authenticationExceptionHandler));
// .jwt().jwtAuthenticationConverter(jwtAccessTokenConverter);
但是,由于我不得不删除 .jwt()
,所以现在无法提供我自己的令牌转换器,我仍然不清楚默认转换器为我提供了什么。
另外,我不清楚为什么我需要使用JwtIssuerAuthenticationManagerResolver
的第三个构造函数并提供我自己的AuthenticationManagerResolver<String>
?如果我上面的代码正在编译,为什么我需要这样做?
【问题讨论】:
【参考方案1】:如果您已使用JwtAuthenticationProvider
配置资源服务器,则需要JwtDecoder
(因为它需要特定的JwtDecoder
)。例如,如果您这样做,就会发生这种情况:
http
...
.oauth2ResourceServer(oauth2 -> oauth2
.authenticationManagerResolver(authenticationManagerResolver)
.jwt(Customizer.withDefaults())
)
由于authenticationManagerResolver
是在AuthenticationManager
级别分支的替代项,因此您不想使用JwtAuthenticationProvider
。它将由JwtIssuerAuthenticationManagerResolver
内部使用。
在这种情况下删除 .jwt()
以防止配置器接线。
更新
Dynamic Tenants 上的文档中的部分提供了有关各种自定义选项的更多信息。
在您的情况下,如果不使用 .jwt()
,您将无法轻松连接可以自定义返回的授予权限的 JwtAuthenticationConverter
。
JwtIssuerAuthenticationManagerResolver
在内部使用TrustedIssuerJwtAuthenticationManagerResolver
。这就是执行多租户功能的方法,通过从 JWT 中提取 issuer
声明,并根据匹配的 issuer
创建 JwtDecoder
+ new JwtAuthenticationProvider(jwtDecoder)
。
为了自定义JwtAuthenticationProvider
,您必须重新实现这个类,以便您可以将JwtAuthenticationConverter
注入到每个创建的实例中。您将实现 AuthenticationManagerResolver<String>
来执行此操作。叫它CustomTrustedIssuerJwtAuthenticationManagerResolver
(见this line)。
您只需将其提供给JwtIssuerAuthenticationManagerResolver
,如下所示:
String[] issuers = new String[] "https://www.example.com/auth/realms/example";
AuthenticationManagerResolver<String> authenticationManagerResolver =
new CustomTrustedIssuerJwtAuthenticationManagerResolver(issuers);
JwtIssuerAuthenticationManagerResolver jwtIssuerAuthenticationManagerResolver =
new JwtIssuerAuthenticationManagerResolver(authenticationManagerResolver);
...
【讨论】:
目前,我的配置包含.jwt().jwtAuthenticationConverter(jwtAccessTokenConverter)
。这个转换器是一个实现Converter<Jwt, AbstractAuthenticationToken>
的bean。它的工作是从 Jwt 和return new JwtAuthenticationToken(jwt, authorities, principalName)
中提取主体和 GrantedAuthorities。如果我按照您的建议删除.jwt()
,那么我可以在我的配置中的哪里连接这个bean?目前jwtAuthenticationConverter
方法只能在.jwt()
方法之后直接调用。
为了让您了解我为什么要配置此转换器,然后我使用 JwtAuthenticationToken
执行诸如使用我开发的自定义 @PreAuthorize
代码来授权 REST 控制器端点调用之类的评估JwtAuthenticationToken
中包含的 GrantedAuthorities。
所以,我缺少的部分是,通过使用JwtIssuerAuthenticationManagerResolver
的方法,我如何才能访问JwtAuthenticationToken
的实例,以及该实例的配置位置和方式?我希望我对我目前所做的事情和原因的解释很清楚。
我必须检查以确保它适用于 5.3(如果这是您使用的版本,请从您的文档链接中),但您可以尝试添加 @Bean public JwtAuthenticationConverter jwtAuthenticationConverter() ...
。
我们使用的是 Spring Cloud 2020.0.3 和 Spring Boot 2.5.6。因此,通过这些 pom 导入,我们使用 Spring Security 5.5.3。以上是关于实现 OAuth 2.0 资源服务器多租户时遇到错误的主要内容,如果未能解决你的问题,请参考以下文章
OAuth 2.0实现分布式认证授权-资源微服务实现用户鉴权拦截