我尝试使用 Spring Boot 3.4.1 运行 oAuth 的所有 3 个 oAuth 组件。我使用oauth-server-docs和客户端 + 资源服务器文档来实现。
oAuth 客户端可以有两种模式:
- 使用 OAuth 2.0 或 OpenID Connect 1.0 登录用户,以及
- 获取用户访问令牌(使用 RestClient/WebClient)。
我想同时使用两者,根据文档,这是可能的。
我的代码 - 所有组件 - 都在 GitHub 上:https://github.com/OhadR/oAuth2-sample
除了一件事之外,一切似乎都运行正常:当客户端应用程序在用户登录之前尝试调用资源服务器时,资源服务器返回 401(我认为这是可以的),但是客户端应用程序不会重定向到 oauth 服务器,而是只返回 500。
我曾尝试调试 Spring,但没有成功。
我看到在中OAuth2AuthorizationRequestRedirectFilter.doFilterInternal()
,当其余过滤器链抛出异常时,Spring 会检查是否存在异常ClientAuthorizationRequiredException
,如果是,则检查是否存在sendRedirectForAuthorization()
。但在我的例子中,由于某种原因,这段代码从未运行过(我在那里设置了一个断点,但它从未在那里停止)。
另外,我发现AuthorizationCodeOAuth2AuthorizedClientProvider.authorize()
应该抛出 的ClientAuthorizationRequiredException
并没有被调用。(它只在“登录”流程中被调用,即在用户登录后,客户端应用调用资源服务器时)。
什么起作用?上面的模式 (1),意味着客户端应用程序有一个“登录”按钮,它调用 /login/oauth2/code/{registrationId}。这会重定向到 oauth 服务器,用户登录,然后当客户端应用程序尝试调用资源服务器时,它会正常工作,并得到 200 响应。
但模式 (2) 不起作用。我遗漏了什么?
谢谢!
我们将发送请求的服务器称为
F
中间的应用程序G
和最后的资源服务器RS
。由于
RS
配置为 OAuth2 资源服务器(在 Spring Security DSL 中),它期望使用它信任的授权服务器(或在多租户情况下它信任的授权服务器之一)颁发的令牌oauth2ResourceServer
来授权请求。Bearer
访问令牌会颁发给 OAuth2 客户端,客户端可以是
G
F
如果G
配置为转发从F
有两种主要的 OAuth2 流程来获取访问令牌(都
F
可以G
使用其中任意一种):RS
信任 OAuth2 客户端(F
或G
),它知道如何处理资源,如果涉及最终用户,则进行预先验证Spring Security 为实现上述各种选项提供了完全的灵活性。但是需要考虑以下几点:
F
在最终用户设备(React、Vue、Angular、移动应用程序等)上运行,现在建议不要将其配置为 OAuth2 客户端。使用该next-auth
库的下一个应用程序无需担心,因为 OAuth2 客户端在应用程序的服务器端(Node 实例)上运行。它可以配置为机密客户端,并将令牌安全地保存在服务器上。G
可能是 OAuth2 BFF:后端的中间件,用于在最终用户设备上运行的应用程序的基于会话的授权与下游资源服务器的基于令牌的授权之间架起桥梁。我在 Baeldung 上写了一个教程,介绍如何将 Spring Cloud Gateway 配置为 OAuth2 BFF:使用oauth2Login
(授权码和刷新令牌流)和TokenRelay=
过滤器(在路由请求时用会话中的访问令牌替换会话 cookie)G
可能会使用不同于 OAuth2 的东西来验证用户身份(formLogin
?)并使用具有客户端凭据的 OAuth2 客户端registration
来授权其请求RS
F
是 oauth2 客户端,G
则可以使用访问令牌授权收到的请求。如果希望使用G
收到的令牌授权其发送的请求,则无需在 上进行 OAuth2 客户端配置G
:令牌取自安全上下文(这是我在微服务架构中经常用于资源服务器间通信的东西)G
配置两个不同的条目:registration
authorization_code
验证用户身份client_credentials
批准其请求RS
在
G
、RestClient
和WebClient
请求授权并不直接与会话状态相关,我们应该分别提供ClientHttpRequestInterceptor
或ExchangeFilterFunction
使用Bearer
令牌设置授权标头。目前,Spring Security 提供
OAuth2ClientHttpRequestInterceptor
和ServerOAuth2AuthorizedClientExchangeFilterFunction
(在 servlet 中ServletOAuth2AuthorizedClientExchangeFilterFunction
使用时)。它们使用通过客户端获取的令牌设置授权标头(可以是应用程序中的授权代码注册,也可以是任何类型的应用程序中的客户端凭据注册)。WebClient
registration
oauth2Login
当使用授权码注册时,此功能要求会话获得授权(用户必须已登录)。因此,委托部分处理任务的端点
RS
必须使用 进行保护isAuthenticated()
,否则内部请求将使用 进行应答401
。仅当接收请求的端点配置了(或需要任何权限)时,
302 Redirect to login
才会在应用程序中发生这种情况。此应用程序内部的 REST 客户端发送的请求不受影响(上面提到的请求拦截器/过滤函数的情况,您为此而抓狂)。oauth2Login
isAuthenticated()
如果想要在安全上下文中转发访问令牌,我们需要自己编写一个
ClientHttpRequestInterceptor
(ExchangeFilterFunction
没什么复杂的)。我创建了一个 Spring Boot 启动器,仅使用应用程序属性即可自动配置
RestClient
具有WebClient
OAuth2 授权(以及 HTTP 代理,如果需要)的 bean。您应该关注一下,无论您选择哪种方式,它都可能让您的生活更轻松。在 GitHub 上与 OP 进行一些讨论后,我添加了一些代码来补充@ch4mp 的优秀答案。
OP 正在使用
org.springframework.web.client.RestClient
。直到最近,它还不支持 OAuth,因此需要使用授权标头。但是在 Spring Security 6.4 中 RestClient 支持 OAuth2,RestClient 可以像这样配置
并且控制器方法可以更改为
完整的控制器代码