实现 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&lt;Jwt, AbstractAuthenticationToken&gt; 的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实现分布式认证授权-资源微服务实现用户鉴权拦截

OAuth 2.0实现分布式认证授权-资源服务器的配置

OAuth 2.0实现分布式认证授权-授权的验证配置

OAuth 2.0实现分布式认证授权-jwt的配置

OAuth 2.0在微服务中:当资源服务器与另一个资源服务器通信时

OAuth 2.0实现分布式认证授权-网关搭建