вариант
ДомДом Skill Безопасность azure-security-keyvault-secrets-java

azure-security-keyvault-secrets-java

microsoft/skills microsoft/skills

Храните, извлекайте и управляйте секретными данными, такими как пароли, ключи API и строки подключения, с помощью Azure Key Vault и Java SDK.

...Расширить все
0
Обновлено время 18 сентября 2026 г.

Секреты Azure Key Vault (Java)

Безопасное хранение и управление секретными данными, такими как пароли, ключи API и строки подключения.

Установка


    com.azure
    azure-security-keyvault-secrets
    4.9.0

Создание клиента

import com.azure.core.credential.TokenCredential;
import com.azure.identity.AzureIdentityEnvVars;
import com.azure.identity.DefaultAzureCredentialBuilder;
import com.azure.identity.ManagedIdentityCredentialBuilder;
import com.azure.security.keyvault.secrets.SecretClient;
import com.azure.security.keyvault.secrets.SecretClientBuilder;

// Локальная разработка: DefaultAzureCredential. Производственная среда: установите AZURE_TOKEN_CREDENTIALS=prod или AZURE_TOKEN_CREDENTIALS=
TokenCredential credential = new DefaultAzureCredentialBuilder()
    .requireEnvVars(AzureIdentityEnvVars.AZURE_TOKEN_CREDENTIALS)
    .build();
// Или используйте конкретные учетные данные напрямую в производственной среде:
// См. https://learn.microsoft.com/java/api/overview/azure/identity-readme?view=azure-java-stable#credential-classes
// TokenCredential credential = new ManagedIdentityCredentialBuilder().build();

// Синхронный клиент
SecretClient secretClient = new SecretClientBuilder()
    .vaultUrl("https://.vault.azure.net")
    .credential(credential)
    .buildClient();

// Асинхронный клиент
SecretAsyncClient secretAsyncClient = new SecretClientBuilder()
    .vaultUrl("https://.vault.azure.net")
    .credential(credential)
    .buildAsyncClient();

Создание/установка секрета

import com.azure.security.keyvault.secrets.models.KeyVaultSecret;

// Простой секрет
KeyVaultSecret secret = secretClient.setSecret("database-password", "P@ssw0rd123!");
System.out.println("Имя секрета: " + secret.getName());
System.out.println("Идентификатор секрета: " + secret.getId());

// Секрет с параметрами
KeyVaultSecret secretWithOptions = secretClient.setSecret(
    new KeyVaultSecret("api-key", "sk_live_abc123xyz")
        .setProperties(new SecretProperties()
            .setContentType("application/json")
            .setExpiresOn(OffsetDateTime.now().plusYears(1))
            .setNotBefore(OffsetDateTime.now())
            .setEnabled(true)
            .setTags(Map.of(
                "environment", "production",
                "service", "payment-api"
            ))
        )
);

Получение секрета

// Получить последнюю версию
KeyVaultSecret secret = secretClient.getSecret("database-password");
String value = secret.getValue();
System.out.println("Значение секрета: " + value);

// Получить конкретную версию
KeyVaultSecret specificVersion = secretClient.getSecret("database-password", "");

// Получить только свойства (без значения)
SecretProperties props = secretClient.getSecret("database-password").getProperties();
System.out.println("Включено: " + props.isEnabled());
System.out.println("Создано: " + props.getCreatedOn());

Обновление свойств секрета

// Получить секрет
KeyVaultSecret secret = secretClient.getSecret("api-key");

// Обновить свойства (значение обновить нельзя — вместо этого создайте новую версию)
secret.getProperties()
    .setEnabled(false)
    .setExpiresOn(OffsetDateTime.now().plusMonths(6))
    .setTags(Map.of("status", "rotating"));

SecretProperties updated = secretClient.updateSecretProperties(secret.getProperties());
System.out.println("Обновлено: " + updated.getUpdatedOn());

Список секретов

import com.azure.core.util.paging.PagedIterable;
import com.azure.security.keyvault.secrets.models.SecretProperties;

// Список всех секретов (только свойства, без значений)
for (SecretProperties secretProps : secretClient.listPropertiesOfSecrets()) {
    System.out.println("Секрет: " + secretProps.getName());
    System.out.println("  Включено: " + secretProps.isEnabled());
    System.out.println("  Дата создания: " + secretProps.getCreatedOn());
    System.out.println("  Тип содержимого: " + secretProps.getContentType());
    
    // Получить значение, если необходимо
    if (secretProps.isEnabled()) {
        KeyVaultSecret fullSecret = secretClient.getSecret(secretProps.getName());
        System.out.println("  Значение: " + fullSecret.getValue().substring(0, 5) + "...");
    }
}

// Перечислить версии секрета
for (SecretProperties version : secretClient.listPropertiesOfSecretVersions("database-password")) {
    System.out.println("Версия: " + version.getVersion());
    System.out.println("Создана: " + version.getCreatedOn());
    System.out.println("Включена: " + version.isEnabled());
}

Удаление секрета

import com.azure.core.util.polling.SyncPoller;
import com.azure.security.keyvault.secrets.models.DeletedSecret;

// Начало удаления (возвращает опросчик для хранилищ с включенной функцией мягкого удаления)
SyncPoller deletePoller = secretClient.beginDeleteSecret("old-secret");

// Ожидание удаления
DeletedSecret deletedSecret = deletePoller.poll().getValue();
System.out.println("Удалено: " + deletedSecret.getDeletedOn());
System.out.println("Запланированная очистка: " + deletedSecret.getScheduledPurgeDate());

deletePoller.waitForCompletion();

Восстановление удалённого секрета

// Список удалённых секретов
for (DeletedSecret deleted : secretClient.listDeletedSecrets()) {
    System.out.println("Удалён: " + deleted.getName());
    System.out.println("Дата удаления: " + deleted.getDeletedOn());
}

// Восстановление удалённого секрета
SyncPoller recoverPoller = secretClient.beginRecoverDeletedSecret("old-secret");
recoverPoller.waitForCompletion();

KeyVaultSecret recovered = recoverPoller.getFinalResult();
System.out.println("Восстановлено: " + recovered.getName());

Окончательное удаление удалённого секрета

// Окончательное удаление (восстановление невозможно)
secretClient.purgeDeletedSecret("old-secret");

// Сначала получить информацию об удаленном секрете
DeletedSecret deleted = secretClient.getDeletedSecret("old-secret");
System.out.println("Будет удалено: " + deleted.getName());
secretClient.purgeDeletedSecret("old-secret");

Резервное копирование и восстановление

// Резервное копирование секрета (все версии)
byte[] backup = secretClient.backupSecret("important-secret");

// Сохранение в файл
Files.write(Paths.get("secret-backup.blob"), backup);

// Восстановление из резервной копии
byte[] backupData = Files.readAllBytes(Paths.get("secret-backup.blob"));
KeyVaultSecret restored = secretClient.restoreSecretBackup(backupData);
System.out.println("Восстановлено: " + restored.getName());

Асинхронные операции

SecretAsyncClient asyncClient = new SecretClientBuilder()
    .vaultUrl("https://.vault.azure.net")
    .credential(new DefaultAzureCredentialBuilder().build())
    .buildAsyncClient();

// Асинхронная настройка секрета
asyncClient.setSecret("async-secret", "async-value")
    .subscribe(
        secret -> System.out.println("Создан: " + secret.getName()),
        error -> System.out.println("Ошибка: " + error.getMessage())
    );

// Асинхронное получение секрета
asyncClient.getSecret("async-secret")
    .subscribe(secret -> System.out.println("Значение: " + secret.getValue()));

// Асинхронный вывод списка секретов
asyncClient.listPropertiesOfSecrets()
    .doOnNext(props -> System.out.println("Найден: " + props.getName()))
    .subscribe();

Шаблоны конфигурации

Загрузка нескольких секретов

public class ConfigLoader {
    private final SecretClient client;
    
    public ConfigLoader(String vaultUrl) {
        this.client = new SecretClientBuilder()
            .vaultUrl(vaultUrl)
            .credential(new DefaultAzureCredentialBuilder().build())
            .buildClient();
    }
    
    public Map loadSecrets(List secretNames) {
        Map secrets = new HashMap<>();
        for (String name : secretNames) {
            try {
                KeyVaultSecret secret = client.getSecret(name);
                secrets.put(name, secret.getValue());
            } catch (ResourceNotFoundException e) {
                System.out.println("Секрет не найден: " + name);
            }
        }
        return secrets;
    }
}

// Использование
ConfigLoader loader = new ConfigLoader("https://my-vault.vault.azure.net");
Map config = loader.loadSecrets(
    Arrays.asList("db-connection-string", "api-key", "jwt-secret")
);

Шаблон ротации секретов

public void rotateSecret(String secretName, String newValue) {
    // Получить текущий секрет
    KeyVaultSecret current = secretClient.getSecret(secretName);
    
    // Отключить старую версию
    current.getProperties().setEnabled(false);
    secretClient.updateSecretProperties(current.getProperties());
    
    // Создание новой версии с новым значением
    KeyVaultSecret newSecret = secretClient.setSecret(secretName, newValue);
    System.out.println("Версия обновлена до: " + newSecret.getProperties().getVersion());
}

Обработка ошибок

import com.azure.core.exception.HttpResponseException;
import com.azure.core.exception.ResourceNotFoundException;

try {
    KeyVaultSecret secret = secretClient.getSecret("my-secret");
    System.out.println("Значение: " + secret.getValue());
} catch (ResourceNotFoundException e) {
    System.out.println("Секрет не найден");
} catch (HttpResponseException e) {
    int status = e.getResponse().getStatusCode();
    if (status == 403) {
        System.out.println("Доступ запрещен — проверьте права доступа");
    } else if (status == 429) {
        System.out.println("Ограничение частоты запросов — попробуйте позже");
    } else {
        System.out.println("Ошибка HTTP: " + status);
    }
}

Скрытые свойства

Свойство Описание
name Имя секрета
значение Значение секрета (строка)
id Полный идентификатор URL
contentType Подсказка типа MIME
включено Можно ли получить секрет
notBefore Время активации
expiresOn Время истечения срока действия
createdOn Время создания
updatedOn Время последнего обновления
recoveryLevel Уровень восстановления после временного удаления
теги Пользовательские метаданные

Переменные среды

AZURE_KEYVAULT_URL=https://.vault.azure.net  # Требуется для URL хранилища
AZURE_TOKEN_CREDENTIALS=prod  # Требуется только в том случае, если DefaultAzureCredential используется в производственной среде

Рекомендации

  1. Включение функции «Мягкое удаление » — защита от случайного удаления
  2. Используйте теги — помечайте секретные данные тегами «среда», «служба», «владелец»
  3. Установите срок действия — используйте setExpiresOn() для учетных данных, которые должны обновляться
  4. Тип содержимого — задайте contentType для указания формата (например, application/json)
  5. Управление версиями — не удаляйте старые версии сразу же при ротации
  6. Регистрация доступа — включите диагностическое ведение журнала в Key Vault
  7. Принципминимальных привилегий — используйте отдельные хранилища для разных сред

Распространенные типы секретов

// Строка подключения к базе данных
secretClient.setSecret(new KeyVaultSecret("db-connection", 
    "Server=myserver.database.windows.net;Database=mydb;...")
    .setProperties(new SecretProperties()
        .setContentType("text/plain")
        .setTags(Map.of("type", "connection-string"))));

// Ключ API
secretClient.setSecret(new KeyVaultSecret("stripe-api-key", "sk_live_...")
    .setProperties(new SecretProperties()
        .setContentType("text/plain")
        .setExpiresOn(OffsetDateTime.now().plusYears(1))));

// Конфигурация JSON
secretClient.setSecret(new KeyVaultSecret("app-config", 
    "{\"endpoint\":\"https://...\",\"key\":\"...\"}")
    .setProperties(new SecretProperties()
        .setContentType("application/json")));

// Пароль сертификата
secretClient.setSecret(new KeyVaultSecret("cert-password", "CertP@ss!")
    .setProperties(new SecretProperties()
        .setContentType("text/plain")
        .setTags(Map.of("certificate", "my-cert"))));

Фразы-триггеры

  • «Секреты Key Vault в Java», «управление секретами в Java»
  • «хранение пароля», «хранение ключа API», «строка подключения»
  • «извлечение секрета», «ротация секрета»
  • «Секреты Azure», «секреты хранилища»
Посмотреть на GitHub
---
name: azure-security-keyvault-secrets-java
description: Store, retrieve, and manage secrets like passwords, API keys, and connection strings using Azure Key Vault with the Java SDK.
license: MIT
---

# Azure Key Vault Secrets (Java)

Securely store and manage secrets like passwords, API keys, and connection strings.

## Installation

```xml
<dependency>
    <groupId>com.azure</groupId>
    <artifactId>azure-security-keyvault-secrets</artifactId>
    <version>4.9.0</version>
</dependency>
```

## Client Creation

```java
import com.azure.core.credential.TokenCredential;
import com.azure.identity.AzureIdentityEnvVars;
import com.azure.identity.DefaultAzureCredentialBuilder;
import com.azure.identity.ManagedIdentityCredentialBuilder;
import com.azure.security.keyvault.secrets.SecretClient;
import com.azure.security.keyvault.secrets.SecretClientBuilder;

// Local dev: DefaultAzureCredential. Production: set AZURE_TOKEN_CREDENTIALS=prod or AZURE_TOKEN_CREDENTIALS=<specific_credential>
TokenCredential credential = new DefaultAzureCredentialBuilder()
    .requireEnvVars(AzureIdentityEnvVars.AZURE_TOKEN_CREDENTIALS)
    .build();
// Or use a specific credential directly in production:
// See https://learn.microsoft.com/java/api/overview/azure/identity-readme?view=azure-java-stable#credential-classes
// TokenCredential credential = new ManagedIdentityCredentialBuilder().build();

// Sync client
SecretClient secretClient = new SecretClientBuilder()
    .vaultUrl("https://<vault-name>.vault.azure.net")
    .credential(credential)
    .buildClient();

// Async client
SecretAsyncClient secretAsyncClient = new SecretClientBuilder()
    .vaultUrl("https://<vault-name>.vault.azure.net")
    .credential(credential)
    .buildAsyncClient();
```

## Create/Set Secret

```java
import com.azure.security.keyvault.secrets.models.KeyVaultSecret;

// Simple secret
KeyVaultSecret secret = secretClient.setSecret("database-password", "P@ssw0rd123!");
System.out.println("Secret name: " + secret.getName());
System.out.println("Secret ID: " + secret.getId());

// Secret with options
KeyVaultSecret secretWithOptions = secretClient.setSecret(
    new KeyVaultSecret("api-key", "sk_live_abc123xyz")
        .setProperties(new SecretProperties()
            .setContentType("application/json")
            .setExpiresOn(OffsetDateTime.now().plusYears(1))
            .setNotBefore(OffsetDateTime.now())
            .setEnabled(true)
            .setTags(Map.of(
                "environment", "production",
                "service", "payment-api"
            ))
        )
);
```

## Get Secret

```java
// Get latest version
KeyVaultSecret secret = secretClient.getSecret("database-password");
String value = secret.getValue();
System.out.println("Secret value: " + value);

// Get specific version
KeyVaultSecret specificVersion = secretClient.getSecret("database-password", "<version-id>");

// Get only properties (no value)
SecretProperties props = secretClient.getSecret("database-password").getProperties();
System.out.println("Enabled: " + props.isEnabled());
System.out.println("Created: " + props.getCreatedOn());
```

## Update Secret Properties

```java
// Get secret
KeyVaultSecret secret = secretClient.getSecret("api-key");

// Update properties (cannot update value - create new version instead)
secret.getProperties()
    .setEnabled(false)
    .setExpiresOn(OffsetDateTime.now().plusMonths(6))
    .setTags(Map.of("status", "rotating"));

SecretProperties updated = secretClient.updateSecretProperties(secret.getProperties());
System.out.println("Updated: " + updated.getUpdatedOn());
```

## List Secrets

```java
import com.azure.core.util.paging.PagedIterable;
import com.azure.security.keyvault.secrets.models.SecretProperties;

// List all secrets (properties only, no values)
for (SecretProperties secretProps : secretClient.listPropertiesOfSecrets()) {
    System.out.println("Secret: " + secretProps.getName());
    System.out.println("  Enabled: " + secretProps.isEnabled());
    System.out.println("  Created: " + secretProps.getCreatedOn());
    System.out.println("  Content-Type: " + secretProps.getContentType());
    
    // Get value if needed
    if (secretProps.isEnabled()) {
        KeyVaultSecret fullSecret = secretClient.getSecret(secretProps.getName());
        System.out.println("  Value: " + fullSecret.getValue().substring(0, 5) + "...");
    }
}

// List versions of a secret
for (SecretProperties version : secretClient.listPropertiesOfSecretVersions("database-password")) {
    System.out.println("Version: " + version.getVersion());
    System.out.println("Created: " + version.getCreatedOn());
    System.out.println("Enabled: " + version.isEnabled());
}
```

## Delete Secret

```java
import com.azure.core.util.polling.SyncPoller;
import com.azure.security.keyvault.secrets.models.DeletedSecret;

// Begin delete (returns poller for soft-delete enabled vaults)
SyncPoller<DeletedSecret, Void> deletePoller = secretClient.beginDeleteSecret("old-secret");

// Wait for deletion
DeletedSecret deletedSecret = deletePoller.poll().getValue();
System.out.println("Deleted on: " + deletedSecret.getDeletedOn());
System.out.println("Scheduled purge: " + deletedSecret.getScheduledPurgeDate());

deletePoller.waitForCompletion();
```

## Recover Deleted Secret

```java
// List deleted secrets
for (DeletedSecret deleted : secretClient.listDeletedSecrets()) {
    System.out.println("Deleted: " + deleted.getName());
    System.out.println("Deletion date: " + deleted.getDeletedOn());
}

// Recover deleted secret
SyncPoller<KeyVaultSecret, Void> recoverPoller = secretClient.beginRecoverDeletedSecret("old-secret");
recoverPoller.waitForCompletion();

KeyVaultSecret recovered = recoverPoller.getFinalResult();
System.out.println("Recovered: " + recovered.getName());
```

## Purge Deleted Secret

```java
// Permanently delete (cannot be recovered)
secretClient.purgeDeletedSecret("old-secret");

// Get deleted secret info first
DeletedSecret deleted = secretClient.getDeletedSecret("old-secret");
System.out.println("Will purge: " + deleted.getName());
secretClient.purgeDeletedSecret("old-secret");
```

## Backup and Restore

```java
// Backup secret (all versions)
byte[] backup = secretClient.backupSecret("important-secret");

// Save to file
Files.write(Paths.get("secret-backup.blob"), backup);

// Restore from backup
byte[] backupData = Files.readAllBytes(Paths.get("secret-backup.blob"));
KeyVaultSecret restored = secretClient.restoreSecretBackup(backupData);
System.out.println("Restored: " + restored.getName());
```

## Async Operations

```java
SecretAsyncClient asyncClient = new SecretClientBuilder()
    .vaultUrl("https://<vault>.vault.azure.net")
    .credential(new DefaultAzureCredentialBuilder().build())
    .buildAsyncClient();

// Set secret async
asyncClient.setSecret("async-secret", "async-value")
    .subscribe(
        secret -> System.out.println("Created: " + secret.getName()),
        error -> System.out.println("Error: " + error.getMessage())
    );

// Get secret async
asyncClient.getSecret("async-secret")
    .subscribe(secret -> System.out.println("Value: " + secret.getValue()));

// List secrets async
asyncClient.listPropertiesOfSecrets()
    .doOnNext(props -> System.out.println("Found: " + props.getName()))
    .subscribe();
```

## Configuration Patterns

### Load Multiple Secrets

```java
public class ConfigLoader {
    private final SecretClient client;
    
    public ConfigLoader(String vaultUrl) {
        this.client = new SecretClientBuilder()
            .vaultUrl(vaultUrl)
            .credential(new DefaultAzureCredentialBuilder().build())
            .buildClient();
    }
    
    public Map<String, String> loadSecrets(List<String> secretNames) {
        Map<String, String> secrets = new HashMap<>();
        for (String name : secretNames) {
            try {
                KeyVaultSecret secret = client.getSecret(name);
                secrets.put(name, secret.getValue());
            } catch (ResourceNotFoundException e) {
                System.out.println("Secret not found: " + name);
            }
        }
        return secrets;
    }
}

// Usage
ConfigLoader loader = new ConfigLoader("https://my-vault.vault.azure.net");
Map<String, String> config = loader.loadSecrets(
    Arrays.asList("db-connection-string", "api-key", "jwt-secret")
);
```

### Secret Rotation Pattern

```java
public void rotateSecret(String secretName, String newValue) {
    // Get current secret
    KeyVaultSecret current = secretClient.getSecret(secretName);
    
    // Disable old version
    current.getProperties().setEnabled(false);
    secretClient.updateSecretProperties(current.getProperties());
    
    // Create new version with new value
    KeyVaultSecret newSecret = secretClient.setSecret(secretName, newValue);
    System.out.println("Rotated to version: " + newSecret.getProperties().getVersion());
}
```

## Error Handling

```java
import com.azure.core.exception.HttpResponseException;
import com.azure.core.exception.ResourceNotFoundException;

try {
    KeyVaultSecret secret = secretClient.getSecret("my-secret");
    System.out.println("Value: " + secret.getValue());
} catch (ResourceNotFoundException e) {
    System.out.println("Secret not found");
} catch (HttpResponseException e) {
    int status = e.getResponse().getStatusCode();
    if (status == 403) {
        System.out.println("Access denied - check permissions");
    } else if (status == 429) {
        System.out.println("Rate limited - retry later");
    } else {
        System.out.println("HTTP error: " + status);
    }
}
```

## Secret Properties

| Property | Description |
|----------|-------------|
| `name` | Secret name |
| `value` | Secret value (string) |
| `id` | Full identifier URL |
| `contentType` | MIME type hint |
| `enabled` | Whether secret can be retrieved |
| `notBefore` | Activation time |
| `expiresOn` | Expiration time |
| `createdOn` | Creation timestamp |
| `updatedOn` | Last update timestamp |
| `recoveryLevel` | Soft-delete recovery level |
| `tags` | User-defined metadata |

## Environment Variables

```bash
AZURE_KEYVAULT_URL=https://<vault-name>.vault.azure.net  # Required for vault URL
AZURE_TOKEN_CREDENTIALS=prod  # Required only if DefaultAzureCredential is used in production
```

## Best Practices

1. **Enable Soft Delete** - Protects against accidental deletion
2. **Use Tags** - Tag secrets with environment, service, owner
3. **Set Expiration** - Use `setExpiresOn()` for credentials that should rotate
4. **Content Type** - Set `contentType` to indicate format (e.g., `application/json`)
5. **Version Management** - Don't delete old versions immediately during rotation
6. **Access Logging** - Enable diagnostic logging on Key Vault
7. **Least Privilege** - Use separate vaults for different environments

## Common Secret Types

```java
// Database connection string
secretClient.setSecret(new KeyVaultSecret("db-connection", 
    "Server=myserver.database.windows.net;Database=mydb;...")
    .setProperties(new SecretProperties()
        .setContentType("text/plain")
        .setTags(Map.of("type", "connection-string"))));

// API key
secretClient.setSecret(new KeyVaultSecret("stripe-api-key", "sk_live_...")
    .setProperties(new SecretProperties()
        .setContentType("text/plain")
        .setExpiresOn(OffsetDateTime.now().plusYears(1))));

// JSON configuration
secretClient.setSecret(new KeyVaultSecret("app-config", 
    "{\"endpoint\":\"https://...\",\"key\":\"...\"}")
    .setProperties(new SecretProperties()
        .setContentType("application/json")));

// Certificate password
secretClient.setSecret(new KeyVaultSecret("cert-password", "CertP@ss!")
    .setProperties(new SecretProperties()
        .setContentType("text/plain")
        .setTags(Map.of("certificate", "my-cert"))));
```

## Trigger Phrases

- "Key Vault secrets Java", "secret management Java"
- "store password", "store API key", "connection string"
- "retrieve secret", "rotate secret"
- "Azure secrets", "vault secrets"

Все файлы

0 файлов

Установить azure-security-keyvault-secrets-java

Скачайте файлы навыков и распакуйте их в каталог .claude/skills/.

Скачать ZIP

Клонируйте репозиторий и скопируйте файлы навыка в свой проект.

git clone https://github.com/microsoft/skills/tree/main/.github/plugins/azure-sdk-java/skills/azure-security-keyvault-secrets-java # Copy SKILL.md to your .claude/skills/ directory

Копировать Копировать
Быстрая настройка: Скопируйте папку со скиллом в каталог .claude/skills/ Claude автоматически обнаружит и начнёт использовать этот скилл
Репозиторий microsoft/skills

Похожие навыки

gmgn-portfolio
Обновлено время 1 июля 2026 г.
zeroize-audit
Обновлено время 1 июля 2026 г.
device-integrity
Обновлено время 29 июня 2026 г.
flutter-use-http-package
Обновлено время 30 июня 2026 г.
OR