春季安全OAuth2客户凭证赠款
#安全 #java #oauth2 #springsecurity

概述

当没有明确的资源所有者或资源所有者与客户无法区分时该怎么办?这是一个相当普遍的情况。例如,当需要在后端系统之间进行直接通信。本文将介绍OAuth2.0客户凭证授权。

oauth2.0文档描述客户凭证授予:

客户使用客户凭证赠款类型在用户的上下文之外获取访问令牌。客户通常被客户使用来访问有关自己的资源,而不是访问用户的资源。

在本文中,您将了解有关具有春季安全性的OAuth2客户凭证赠款,从而允许服务无需认证的用户就可以安全地进行互操作。

oauth2客户端凭证授权比授权代码授权更为简单,通常用于CRON任务和其他类型的后端数据处理等操作。

客户凭证赠款流

当应用程序请求访问令牌以访问其他资源时,使用客户端凭据,而不是代表用户进行授权。

请求参数

Grant_Typeï¼必需¼

必须将grant_type参数设置为client_credentials

Scopeï¼可选

您的服务可以支持客户凭证赠款的不同范围。

客户端验证设施唯一必需¼

客户需要对此请求进行身份验证。通常,该服务将允许其他请求参数client_idclient_secret,或在HTTP Basic Auth标题中接受客户ID和秘密。

Image description

ðâ€注意:如果您不想阅读直到最后,您可以在此处查看source code

OAuth2授权服务器

在这里,我们使用Spring Authorization Server构建OAuth2授权服务器。我不会在这里重复细节。您可以参考文章Using JWT with Spring Security OAuth2来构建授权服务器。这里仅描述了与以前的授权代码授权授权服务配置的区别。

配置

当我们使用注册的构建器类型创建客户端时,我们将配置客户端以支持客户凭证授权并简单地将其存储在内存中。

@Bean
public RegisteredClientRepository registeredClientRepository() {
  RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
    .clientId("relive-client")
    .clientSecret("{noop}relive-client")
    .clientAuthenticationMethods(s -> {
      s.add(ClientAuthenticationMethod.CLIENT_SECRET_POST);
      s.add(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
    })
    .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
    .redirectUri("http://127.0.0.1:8070/login/oauth2/code/messaging-client-model")
    .scope("message.read")
    .clientSettings(ClientSettings.builder()
                    .requireAuthorizationConsent(true)
                    .requireProofKey(false)
                    .build())
    .tokenSettings(TokenSettings.builder()
                   .accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED)
                   .idTokenSignatureAlgorithm(SignatureAlgorithm.RS256)
                   .accessTokenTimeToLive(Duration.ofSeconds(30 * 60))
                   .refreshTokenTimeToLive(Duration.ofSeconds(60 * 60))
                   .reuseRefreshTokens(true)
                   .build())
    .build();

  return new InMemoryRegisteredClientRepository(registeredClient);
}

上面我们配置了一个OAuth2客户端并指定授权granttype 作为 client_credentials

使用Spring Security构建OAuth2资源服务器

OAuth2资源服务器配置与本文Using JWT with Spring Security OAuth2中的资源服务设置一致,您可以在本文中参考OAuth2资源服务的介绍,或在本文末尾获得本文的源代码地址文章要查看。

配置

OAuth2资源服务器提供/resource/actits 受保护的端点,并使用Spring Security来保护此服务。

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
  http.requestMatchers()
    .antMatchers("/resource/article")
    .and()
    .authorizeRequests()
    .mvcMatchers("/resource/article")
    .access("hasAuthority('SCOPE_message.read')")
    .and()
    .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
  return http.build();
}

请注意,oauth2资源服务/resource/actits endpoint需要“邮件。是“ message.read”而不是“ scope_message.read”。

使用Spring Security构建OAuth2客户端

在本节中,您将使用当前推荐的WebClient请求资源服务,这是Spring的WebFlux软件包的一部分。这是春季的反应性,非阻滞API,您可以在Spring Documentation中阅读有关它的更多信息。

@Scheduled定义的cron任务下,您将使用WebClient提出请求。

Maven依赖性

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  <version>2.6.7</version>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
  <version>2.6.7</version>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-oauth2-client</artifactId>
  <version>2.6.7</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-webflux</artifactId>
  <version>5.3.9</version>
</dependency>
<dependency>
  <groupId>io.projectreactor.netty</groupId>
  <artifactId>reactor-netty</artifactId>
  <version>1.0.9</version>
</dependency>

配置

我们将在application.yml中配置OAuth2授权信息,并指定OAuth2客户端服务端口号:

server:
  port: 8070

spring:
  security:
    oauth2:
      client:
        registration:
          messaging-client-model:
            provider: client-provider
            client-id: relive-client
            client-secret: relive-client
            authorization-grant-type: client_credentials
            client-authentication-method: client_secret_post
            scope: message.read
            client-name: messaging-client-model
        provider:
          client-provider:
            token-uri: http://127.0.0.1:8080/oauth2/token

接下来,我们将创建一个SecurityConfig类,以配置Spring Security Oauth2客户端所需的bean:

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
  http
    .authorizeRequests(authorizeRequests ->
                       authorizeRequests.anyRequest().permitAll()
                      )
    .oauth2Client(withDefaults());
  return http.build();
}

@Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
  ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
  return WebClient.builder()
    .filter(oauth2Client)
    .build();
}

@Bean
OAuth2AuthorizedClientManager authorizedClientManager(ClientRegistrationRepository clientRegistrationRepository,
                                                      OAuth2AuthorizedClientService authorizedClientService) {

  OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder
    .builder()
    .clientCredentials()
    .build();
  AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientService);
  authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

  return authorizedClientManager;
}

我们创建一个WebClient实例,以对资源服务器执行HTTP请求,并将OAuth2授权过滤器添加到WebClient。 AuthorizedClientServiceOAuth2AuthorizedClientManager这是协调OAuth2客户凭证请求的高级控制器类,在这里我要指出,AuthorizedClientServiceOAuth2AuthorizedClientManager是专门设计的类,专门用于在Httpservletrequest的上下文之外使用。

来自Spring Documentation

DefaultoAuth2AuthorizedClientManager旨在用于HttpservletRequest的上下文。在HttpservletRequest的上下文外操作时,请改用授权ClientserviceoAuth2AuthorizedClientManager。

接下来,我们将创建一个使用@Scheduled注释定义的任务,并注入网络定位来调用资源服务请求:

@Service
public class ArticleJob {

  @Autowired
  private WebClient webClient;

  @Scheduled(cron = "0/2 * * * * ? ")
  public void exchange() {
    List list = this.webClient
      .get()
      .uri("http://127.0.0.1:8090/resource/article")
      .attributes(clientRegistrationId("messaging-client-model"))
      .retrieve()
      .bodyToMono(List.class)
      .block();
    log.info("Call resource server execution result:" + list);
  }
}

客户服务将每2秒发出一次请求,并在控制台上打印结果。

结论

一如既往,本文中使用的源代码可用on GitHub

感谢您的阅读!