Spring Security OAuth2 客户端:从 Azure Key Vault 动态客户端密钥刷新

问题描述 投票:0回答:1

为了保持描述简短,我有以下依赖项:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
    <groupId>com.azure.spring</groupId>
    <artifactId>spring-cloud-azure-starter-keyvault-secrets</artifactId>
</dependency>

我已在

application.yaml
中配置为从 Azure Key Vault 检索 OAuth 客户端所需的客户端密钥,我的 OAuth 客户端如下:

spring:
  cloud:
    azure:
      keyvault:
        secret:
          property-sources:
            - name: key-vault-properties
              endpoint: https://my-key-vault.vault.azure.net
              refresh-interval: 10s
              secret-keys:
                - test-client-secret
  security:
    oauth2:
      client:
        registration:
          my-client:
            - client-id: id
              client-secret: ${test-client-secret}
              authorization-grant-type: client_credentials
              client-authentication-method: client_secret_basic
        provider:
          my-provider:
            - token-uri: https://token-uri.com

这一切都很好。但是,出于安全原因,我需要每年轮换客户端密钥。

我尝试在 Key Vault 中添加新版本的密钥。但当我重新启动应用程序时,我只看到它反映在

ClientRegistration
中。

我的问题:是否可以在不重新启动完整应用程序的情况下动态刷新客户端密钥?如果是这样,我怎样才能实现这一目标?

附加信息:

  • Spring Boot版本:3.3.3

  • Spring Cloud Azure 版本:5.14.0

  • Spring 安全版本:6.3.3

spring-boot spring-security spring-cloud azure-keyvault spring-cloud-azure
1个回答
0
投票

我尝试使用下面的示例 Spring boot 应用程序每 10 分钟轮换一次 Keyvault 密钥,而无需重新启动应用程序。

要安排每年的秘密轮换,您可以在 KeyVaultService 类中指定

fixedRate
参数(以毫秒为单位),如下所示。

@Scheduled(fixedRate = 31_536_000_000L)    // 31,536,000,000 ms = 1 year

KeyVaultService.java:

import com.azure.security.keyvault.secrets.SecretClient;
import com.azure.security.keyvault.secrets.models.KeyVaultSecret;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.scope.refresh.RefreshScope;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.security.SecureRandom;
import java.util.Base64;

@Service
public class KeyVaultService {

    private final SecretClient secretClient;
    private final RefreshScope refreshScope;

    @Autowired
    public KeyVaultService(SecretClient secretClient, RefreshScope refreshScope) {
        this.secretClient = secretClient;
        this.refreshScope = refreshScope;
    }
    public String getSecret(String secretName) {
        try {
            KeyVaultSecret secret = secretClient.getSecret(secretName);
            return secret.getValue();
        } catch (Exception e) {
            return "Failed to retrieve secret: " + e.getMessage();
        }
    }

    @Scheduled(fixedRate = 600_000) // @Scheduled(fixedRate = 31_536_000_000L)
    public void rotateClientSecret() {
        String secretName = "test-client-secrets";
        String newSecretValue = generateSecureSecret();

        try {
            secretClient.setSecret(new KeyVaultSecret(secretName, newSecretValue));
            System.out.println("Successfully rotated the client secret.");
            refreshScope.refreshAll();
        } catch (Exception e) {
            System.err.println("Failed to rotate client secret: " + e.getMessage());
        }
    }
    private String generateSecureSecret() {
        SecureRandom secureRandom = new SecureRandom();
        byte[] bytes = new byte[24];
        secureRandom.nextBytes(bytes);
        return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
    }
}

SecretController.java:

import com.example.keyvaultsecretdemo.service.KeyVaultService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/secrets")
public class SecretController {

    private final KeyVaultService keyVaultService;
    @Autowired
    public SecretController(KeyVaultService keyVaultService) {
        this.keyVaultService = keyVaultService;
    }
    @GetMapping("/get/{secretName}")
    public String getSecret(@PathVariable String secretName) {
        return keyVaultService.getSecret(secretName);
    }
}

KeyVaultConfig.java:

import com.azure.identity.ClientSecretCredential;
import com.azure.identity.ClientSecretCredentialBuilder;
import com.azure.security.keyvault.secrets.SecretClient;
import com.azure.security.keyvault.secrets.SecretClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class KeyVaultConfig {

    @Value("${spring.cloud.azure.keyvault.secret.property-sources[0].endpoint}")
    private String vaultUrl;
    @Value("${spring.cloud.azure.keyvault.secret.property-sources[0].secret-keys[0]}")
    private String secretKey;
    @Value("${spring.security.oauth2.client.registration.my-client.client-id}")
    private String clientId;
    @Value("${spring.security.oauth2.client.registration.my-client.client-secret}")
    private String clientSecret;
    @Value("${spring.security.oauth2.client.registration.my-client.tenant-id}")
    private String tenantId;

    @Bean
    public SecretClient secretClient() {
        ClientSecretCredential clientSecretCredential = new ClientSecretCredentialBuilder()
                .clientId(clientId)
                .clientSecret(clientSecret)
                .tenantId(tenantId)
                .build();

        return new SecretClientBuilder()
                .vaultUrl(vaultUrl)
                .credential(clientSecretCredential)
                .buildClient();
    }
}

application.yml:

spring:
  cloud:
    config:
      enabled: false
    azure:
      keyvault:
        secret:
          property-sources:
            - name: key-vault-properties
              endpoint: https://<keyvaultName>.vault.azure.net/
              refresh-interval: 10s
              secret-keys:
                - test-client-secrets
  security:
    oauth2:
      client:
        registration:
          my-client:
            client-id: <clientID>
            client-secret: ${test-client-secrets}
            tenant-id: <tenantID>
            authorization-grant-type: client_credentials
            client-authentication-method: client_secret_basic
        provider:
          my-provider:
            token-uri: https://login.microsoftonline.com/<tenantID>/oauth2/v2.0/token

pom.xml:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.azure</groupId>
        <artifactId>azure-security-keyvault-secrets</artifactId>
        <version>4.6.0</version>
    </dependency>
    <dependency>
        <groupId>com.azure</groupId>
        <artifactId>azure-identity</artifactId>
        <version>1.14.0</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>com.azure</groupId>
        <artifactId>azure-core</artifactId>
        <version>1.53.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
        <version>3.3.4</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-config</artifactId>
        <version>4.1.3</version>
    </dependency>
    <dependency>
        <groupId>com.azure.spring</groupId>
        <artifactId>spring-cloud-azure-starter-keyvault-secrets</artifactId>
        <version>5.17.1</version>
    </dependency>
    <dependency>
        <groupId>org.apache.camel.springboot</groupId>
        <artifactId>camel-spring-boot-starter</artifactId>
        <version>4.6.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

终端输出:

enter image description here

浏览器输出:

我在浏览器中得到了秘密,如下所示。

http://localhost:8080/api/secrets/get/{secretName}

enter image description here

密钥库秘密:

Azure Keyvault 秘密版本每 10 分钟更新一次,如下所示。

enter image description here

© www.soinside.com 2019 - 2024. All rights reserved.