安全上

我刚刚宣布了新消息学习Spring Security课程,包括全部材料专注于春季安全的新OAuth2堆栈5:

>>查看课程

1.概述

Spring Security 5为Spring WebFlux的非阻塞提供了OAuth2支持WebClient班级。

在本教程中,我们将分析使用此类访问安全资源的不同方法。

此外,我们将在引擎盖下看看,了解Spring如何处理OAuth2授权过程。

2.设置方案

内联与OAuth2规范除了我们的客户之外 - 这是我们在本文中的重点主题 - 我们自然需要授权服务器和资源服务器。

我们可以使用知名的授权提供商,如谷歌或Github。为了更好地理解OAuth2客户机的角色,我们还可以使用我们自己的服务器,在此处提供了一个可用的。我们不会展示完整的配置,因为这不是本教程的主题,了解以下内容就足够了:

  • 授权服务器将是:
    • 运行在端口8081
    • 暴露/ OAuth /授权,/ oauth /令牌OAuth / check_token.实现所需功能的端点
    • 使用示例用户配置(例如。约翰/123.)和一个OAuth客户端(fooClientIdPassword/秘密
  • 资源服务器将与身份验证服务器分开,并将是:
    • 运行在端口8082
    • 服务简单Foo对象受保护的资源/ foos / {id}终点

注意:了解几个Spring项目提供不同的OAuth相关的功能和实现非常重要。我们可以检查每个图书馆提供的内容这个Spring项目矩阵

WebClient所有反应性WebFlux相关功能都是Spring Security 5项目的一部分。因此,我们主要在本文中使用此框架。

3.春天安全5在引擎盖下

为了充分了解前进的例子,知道Spring Security如何在内部管理OAuth2功能是有益的。

此框架提供了以下功能:

  • 依赖于OAuth2提供者帐户将用户登录到应用程序
  • 将我们的服务配置为OAuth2客户端
  • 管理我们的授权程序
  • 自动刷新令牌
  • 如有必要,请存储凭据

Spring Security的OAuth2世界的一些基本概念如下图所示:

3.1。提供者

Spring定义OAuth2提供程序角色,负责暴露OAuth 2.0受保护的资源。

在我们的示例中,我们的身份验证服务将是提供提供商功能的身份验证服务。

3.2。客户注册

一种客户重组是一个包含在OAuth2(或OpenID)提供商中注册的特定客户端的所有相关信息的实体。

在我们的方案中,它将成为在认证服务器中注册的客户端,由此标识bael-client-idID。

3.3。授权客户端

一旦最终用户(aka资源所有者)授予客户端的权限,以访问其资源,OAuth2AuthorizedClient创建实体。

它将负责将访问令牌与客户端注册和资源所有者关联(由主要对象)。

3.4。存储库

此外,Spring Security还提供了存储库类以访问上述实体。

特别是,这是ReactiveClientRegistrationRepository.ServerOAuth2AuthorizedClientRepository类在反应堆中使用,默认情况下,它们使用内存存储器。

弹簧引导2。x创建这些存储库类的bean,并自动将它们添加到上下文中。

3.5。安全网络过滤器链

Spring Security 5中的关键概念之一是反应性SecurityWebFilterChain实体。

随着它的名称表示,它代表了一个链接的集合WebFilter.对象。

当我们在我们的应用程序中启用OAuth2功能时,Spring Security将两个过滤器添加到链中:

  1. 一个过滤器响应授权请求(/ oauth2 /授权/ {registrationid}Uri)或抛出一个clientAuthorizationRequiredException.。对象的引用ReactiveClientRegistrationRepository,它负责创建授权请求以重定向用户代理。
  2. 第二个过滤器取决于我们添加的特性(OAuth2客户端功能或OAuth2登录功能)。在这两种情况下,该过滤器的主要职责是创建OAuth2AuthorizedClient实例,并使用ServerOAuth2AuthorizedClientRepository。

3.6。Web客户端

Web客户端将配置有一个ExchangeFilterFunction.包含对存储库的引用。

它将使用它们获取访问令牌以自动将其添加到请求中。

4. Spring Security 5支持 - 客户端凭据流程

Spring Security允许将我们的应用程序配置为OAuth2客户端。

在这篇文章中,我们将使用WebClient实例来使用“客户端凭据”检索资源。授予类型首先,然后使用“授权代码”流程。

我们必须做的第一件事是配置客户端注册和我们将用来获取访问令牌的提供者。

4.1。客户端和提供者配置

正如我们在OAuth2登录文章,我们可以通过编程的方式配置它,也可以通过使用属性来定义我们的注册来依赖Spring Boot自动配置:

spring.security.oauth2.client.registration.bael.authorization-grant-type = client_credentials spring.security.oauth2.client.registration.bael.client-id = bael-client.bael.client-id = bael-client-id spring.security.oauth2.client.registration。bael.client-secret = bael-secret spring.security.oauth2.client.provider.bael.token-uri = http:// localhost:8085 / oauth /令牌

这些都是我们需要使用的配置来检索资源的所有配置client_credentials流。

4.2。使用WebClient

我们在没有终端用户与我们的应用程序交互的机器对机器通信中使用这种授权类型。

例如,让我们想象我们有一个cr作业试图使用a获取安全资源WebClient在我们的应用程序:

@autowired私有webclient webclient;@scheduled(fixedrate = 5000)public void logresourceserviceresponse(){webclient.get().uri(“http:// localhost:8084 /检索资源”).retrieve().bodytomono(string.class).map(字符串- >“使用客户端凭据检索授予类型:”+字符串).subscribe(logger :: Info);}

4.3。配置WebClient

接下来,让我们设置WebClient.我们在计划任务中公开了实例:

@bean webclient webclient(RecortiveClientRegistrationReposion ClientRegistrations){serveroauth2authorizedclientexchangefilterfunction oauth = new serveroauth2authorizedClientExchangeFilterFunction(ClientRegistrations,New UnauthenticationServeroauth2authorizedClientRepository());oauth.setdefaultClientRegistroniDID(“bael”);return webclient.Builder().Filter(OAuth).build();}

正如我们所说,通过Spring启动将自动创建并将客户端注册存储库添加到上下文中。

接下来要注意的是我们用的是aUnauthenticateServeroauth2authorizedClientRepository.实例。这是因为它没有终端用户将参加此过程,因为它是机器到机器通信。最后,我们表示我们使用了宝贝客户端注册默认情况下。

否则,我们必须在cron作业中定义请求时指定它:

webclient.get().uri(“http:// localhost:8084 /检索uthource”).Attributes(serveroauth2authorizedclientexchangefilterfunction .clientregistrondid(“bael”)).retrieve()// ...

4.4。测试

如果我们使用我们的申请调试启用日志级别,我们就能看到Spring Security为我们做的调用:

o.s.w.r.f.client.ExchangeFunctions: HTTP POST http://localhost:8085/oauth/token o.s.http.codec.json。杰克逊2JsonDecoder: Decoded [{access_token=89cf72cd-183e-48a8-9d08-661584db4310, token_type=bearer, expires_in=41196, scope=read (truncated)...] o.s.w.r.f.client.ExchangeFunctions: HTTP GET http://localhost:8084/retrieve-resource o.s.core.codec.StringDecoder: Decoded "This is the resource!" c.b.w.c.service.WebClientChonJob: We retrieved the following resource using Client Credentials Grant Type: This is the resource!

我们还会注意到第二次任务运行,应用程序请求资源而不要求自上次尚未过期以来首次询问令牌。

5.Spring Security 5支持——使用授权代码流实现

此授予类型通常用于在较低信任的第三方应用程序需要访问资源的情况下。

5.1。客户端和提供者配置

为了使用授权代码流执行OAuth2流程,我们需要为我们的客户端注册和提供者定义更多的属性:

spring.security.oauth2.client.registration.bael.client-name = bael spring.security.oauth2.client.registration.bael.client-id = bael-client-id spring.security.oauth2.client.registration.bael。client-secret = bael-secret spring.security.ouuth2.client.registration.bael .authorization-grant-type = authreatizization_code spring.security.oauth2.client.registration.bael .redirect-uri = http:// localhost  -  8080 /登录/ oauth2 / code / bael spring.security.oauth2.client.provider.bael.token-uri = http:// localhost:8085 / oauth /令牌spring.security.oauth2.client.provider.bael。authorization-uri =http:// localhost:8085 / oauth /授权spring.security.oauth2.client.provider.bael.user-info-uri = http:// localhost:8084 /用户spring.security.oauth2.client.provider.bael。user-name-attribute = name

除了在上一节中使用的属性,我们还需要包括:

  • 在身份验证服务器上进行身份验证的端点
  • 包含用户信息的端点的URL
  • 在验证后,我们的应用程序中将重定向用户代理的端点的URL

当然,对于众所周知的提供者,不需要指定前两点。

重定向端点由Spring Security自动创建。

默认情况下,为其配置的URL是/ [动作] / oauth2 / code / [注册表],只有授权登录允许操作(为了避免无限循环)。

这个端点负责:

  • 接收身份验证代码作为查询参数
  • 使用它来获取访问令牌
  • 创建授权客户端实例
  • 将用户代理重定向回原始端点

5.2。HTTP安全配置

接下来,我们需要配置SecurityWebFilterChain。

最常见的方案是使用Spring Security的OAuth2登录功能来验证用户,并给他们访问我们的端点和资源。

如果这是我们的情况,那么只是在包括这一点oauth2login.指令在serverhtptpsecurity.定义也足以让我们的应用程序作为OAuth2客户端工作:

@Bean public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {http. authorizeexchange () .anyExchange() .authenticated() .oauth2Login();返回http.build ();}

5.3。配置WebClient

现在是时候把我们的WebClient实例:

@bean webclient webclient(restiveClientRegistrationRepository客户端传输,Serveroauth2authorizedClientRepository授权,{serveroauth2authorizedClientExchangeFilterFunction oauth = new serveroauth2authorizedClientExchangeFilterFunction(ClientRegistrations,授权);oauth.setdefaultoauth2authorizedclient(true);return webclient.Builder().Filter(OAuth).build();}

这次我们从上下文中注入了客户端注册存储库和授权的客户端存储库。

我们也可以setdefaultoauth2authorizedClient.选项。有了它,框架将尝试从当前获取客户信息验证在Spring Security中管理对象。

我们必须考虑到,所有的HTTP请求都将包含访问令牌,这可能不是我们想要的行为。

稍后,我们将分析替代方案,以指示客户端一个特定的WebClient交易将使用。

5.4。使用WebClient

授权代码需要一个用户代理,可以解决重定向(例如,浏览器)来执行该过程。

因此,当用户与我们的应用程序交互时,我们使用这种授权类型,通常调用HTTP端点:

@RestController public class ClientRestController {@Autowired WebClient WebClient;@GetMapping("/auth-code") Mono useOauthWithAuthCode() {Mono retrievedResource = webClient.get() .uri("http://localhost:8084/retrieve-resource") .retrieve() .bodyToMono(String.class);retrievedResource返回。map(string -> "我们使用Oauth检索以下资源:" + string);}}

5.5。测试

最后,我们将呼叫端点并通过检查日志条目来分析正在发生的内容。

在调用端点之后,应用程序会验证我们是否还没有在应用程序中进行身份验证:

oss.w.s.adapter . httpwebhandleradapter: HTTP GET "/auth-code"…HTTP/1.1 302发现的位置:/oauth2/authorization/bael

应用程序重定向到授权服务的端点以在提供商的注册管理机构中使用凭据进行身份验证(在我们的情况下,我们将使用bael-user / bael-password):

http / 1.1 302找到位置:http:// localhost:8085 / oauth /授权?response_type = code_id = bael-client-id&state = ...&redirect_uri = http%3a%2f%2f%2flocalhost%3a8080%2flogin%2foauth2%2Fcode%2fbael.

验证后,将用户代理发送回重定向URI,以及代码作为查询参数和第一次发送的状态值(以避免CSRF攻击):

O.S.W.S.ADAPTER.HTTPWEBHANDLERADAPLET:http get“/ login / oauth2 / code / bael?code = ...&state = ...

然后,应用程序使用代码获取访问令牌:

o.s.w.r.f.client.exchangeFunctions:http post http:// localhost:8085 / oauth /令牌

获取用户信息:

o.s.w.r.f.client.exchangeFunctions:http获取http:// localhost:8084 /用户

它将用户代理重定向到原始端点:

http / 1.1 302找到位置:/ auth  - 代码

最后,我们WebClient实例可以成功请求受保护的资源:

o.s.w.r.f.client.exchangeFunctions:http获取http:// localhost:8084 /检索资源O.s.w.r.f.client.exchangeFunctions:响应200ek O.S.Core.Codec.StringDecoder:解码“这是资源!”

6.另一种在呼叫中注册的客户端

早些时候,我们看到了使用setdefaultoauth2authorizedClient.意味着应用程序将在我们与客户端实现的任何呼叫中包含访问令牌。

如果我们从配置中删除此命令,我们需要在定义请求时显式指定客户端注册。

当然,一种方式是使用的clientRegistrondId.正如我们在客户凭据流程的情况下之前所做的那样。

自从我们相关联主要通过授权客户,我们可以获得OAuth2AuthorizedClient实例使用@ EnginalDoauth2authorizedClient.注解:

@getmapping(“/ auth-code-annotated”)mono  umonouthwithauthcodeandannotation(@ logedoauth2authorizedClient(@ bael“)oauth2authorizedClient授权函数)){Mono  RetredResource = webclient.get().ure(”http:// localhost:8084 /检索资源“).Attributes(serveroauth2authorizedclientexchangefilterfunction.outh2authorizedClient(授权)).bodytomono(string.class);returnedresiveResource.map(String  - >“资源:”+ String +“ - 主体关联:”+授权克莱语.principalname()+“ - 令牌将在以下命令at:”+ authorizedclient.getaccesstoken().getexpiresat());}

7.避免OAuth2登录功能

正如我们所说,最常见的情况是依赖于OAuth2授权提供商来在我们的应用程序中登录用户。

但是,如果我们想要避免这种情况,但仍然能够使用OAuth2协议访问安全的资源,该怎么办?然后,我们需要在配置中做一些更改。

对于初学者来说,只是为了清楚地看板,我们可以使用授权行动而不是登录一,定义URI重定向属性时:

spring.security.oauth2.client.registration.bael .redirect-uri = http:// localhost:8080 / login / oauth2 / code / bael

我们还可以删除与用户相关的属性,因为我们不会使用它们来创建主要在我们的应用程序。

现在,我们将配置SecurityWebFilterChain不包括oauth2login.命令,而且,我们将包括oauth2client.一。

即使我们不希望依赖于OAuth2登录,我们仍然希望在访问端点之前对用户进行身份验证。由于这个原因,我们还将包括formLogin在这里指令:

@bean public securitywebfilterchain springsecurityfilterchain(serverhttpsecurity http){http.authorizeexchange().anyexchange().authenticated().anand().oauth2client().and().formlogin();返回http.build ();}

让我们现在运行该应用程序并查看我们使用时会发生什么/ auth-code-annotated端点。

我们首先必须使用表单登录登录我们的应用程序。

之后,该应用程序将我们重定向到授权服务登录,以授予对我们的资源的访问权限。

注意:执行此操作后,我们应该重定向到我们调用的原始端点。尽管如此,春天的安全似乎被重定向到根路径“/”,似乎是一个错误。在触发OAuth2舞蹈之后的以下请求将成功运行。

我们可以在端点响应中看到,这次授权的客户端与名为bael-client-id而不是这一点Bael-user,在身份验证服务中配置的用户后命名。

8.Spring框架支持-手动方式

盒子外面,Spring 5仅提供一个OAuth2相关的服务方法,以便轻松地将承载令牌标题添加到请求中。这是httpheaders#setbearerauth.方法。

现在,我们将看到一个示例,以了解通过手动执行OAuth2舞蹈来获取受保护的资源需要什么。

简单地说,我们需要将两个HTTP请求链接起来:一个请求从授权服务器获得身份验证令牌,另一个请求使用这个令牌获得资源:

@autowired webclient客户端;public mono 获取recuredresource(){string encodedclientdata = base64utils.encodetostring(“bael-client-id:bael-secret”.getbytes());mono  resource = client.post().uri(“localhost:8085 / oauth /令牌”).header(“授权”,“基本”+ EncodedClientData).body(bodyinserters.fromformdata(“grant_type”,“client_crecentials“).Retrieve().bodytomono(jsonnode.class).fodytomono(jsonnode.class).flatmap(tokenresponse  - > {string accesstokenvalue = tokenresponse.get(”Access_Token“).textValue(); returncly.get().uri(”localhost:8084/检索资源“).headers(h  - > h.setbearairauth(accesstokenvalue)).retrieve().bodytomono(string.class);});返回Resource.map(Res  - >“使用手动方法检索资源:”+ RES);}

这个例子主要是要了解oauth2规范之后的请求可以如何繁琐,看看如何setBearerAuth使用方法。

在一个现实生活场景中,我们让春天安全以透明的方式照顾我们所有的努力工作,正如我们在前一节所做的那样。

9.结论

在本教程中,我们已经看到了我们如何将应用程序设置为OAuth2客户端,更具体地,我们如何配置和使用WebClient在全反动堆栈中检索安全资源。

最后但并非最不重要的是,我们分析了Spring Security 5 OAuth2机制如何在内部运行以符合OAuth2规范。

一如既往,完整的例子可在开启GitHub.

安全底部

我刚刚宣布了新消息学习Spring Security课程,包括全部材料专注于春季安全的新OAuth2堆栈5:

>>查看课程
评论在本文上关闭!