OpenIddictKeyConfigurator: dev — прежний RSA-ключ в App_Data (поведение не менялось, шифрование access-token выключено); prod/stage — отдельные X509 сертификаты подписи и шифрования из конфига (OpenIddict:SigningCertPath / EncryptionCertPath / CertPassword, можно env). Нет файла → генерируется persistent self-signed (RSA 2048, 5 лет) и сохраняется в App_Data (volume), а не dev-ephemeral — токены переживают рестарт. Проверено: prod выдаёт 5-сегментный JWE, /api/me 200; рестарт → те же сертификаты (fingerprint совпал), pre-restart токен валиден. dev — 3-сегментный JWT, /api/me 200. docs/openiddict-keys.md. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
4.4 KiB
Ключи OpenIddict (подпись и шифрование токенов)
Токены доступа/обновления подписываются (и в проде шифруются) ключами OpenIddict.
Конфигурация ключей — в OpenIddictKeyConfigurator (src/food-market.api/Infrastructure/Security/),
вызывается из Program.cs внутри AddServer(...).
Development
- Persistent RSA-ключ в
src/food-market.api/App_Data/openiddict-dev-key.xml(один и тот же для подписи и шифрования). - Переживает рестарты — выданные токены остаются валидными между перезапусками.
- Шифрование access-token выключено (
DisableAccessTokenEncryption) — токен это обычный 3-сегментный JWT, удобно дебажить (можно прочитать на jwt.io). - Файл
App_Data/в.gitignore— ключ не коммитится.
Production / Stage
-
Отдельные X509-сертификаты для подписи и шифрования. Access-token шифруется (5-сегментный JWE).
-
Путь к сертификатам — из конфигурации:
Ключ конфига Env-переменная Назначение Дефолт OpenIddict:SigningCertPathOpenIddict__SigningCertPathсертификат подписи App_Data/openiddict-signing.pfxOpenIddict:EncryptionCertPathOpenIddict__EncryptionCertPathсертификат шифрования App_Data/openiddict-encryption.pfxOpenIddict:CertPasswordOpenIddict__CertPasswordпароль PFX (опц.) — -
Если файла нет — генерируется persistent self-signed сертификат (RSA 2048, срок 5 лет) и сохраняется по пути. При следующем старте берётся тот же файл, поэтому ранее выданные токены остаются валидными (нет dev-ephemeral поведения, при котором каждый рестарт инвалидировал бы все токены).
-
App_Dataсмонтирован как volume (api-data:/app/App_Dataвdocker-compose.yml), поэтому сертификаты переживают пересоздание контейнера.
Принести собственные сертификаты
Положить готовые .pfx (с приватным ключом) по путям из конфига и, при наличии пароля,
задать OpenIddict__CertPassword. Приложение их подхватит вместо генерации self-signed.
# пример: смонтировать каталог с сертификатами и указать пути
OpenIddict__SigningCertPath=/run/secrets/oidc-signing.pfx
OpenIddict__EncryptionCertPath=/run/secrets/oidc-encryption.pfx
OpenIddict__CertPassword=<пароль или пусто>
Ротация
- Заменить/удалить
.pfxфайлы (или указать новые пути). - Рестарт API: при отсутствии файла сгенерируется новый сертификат.
- Важно: ротация ключа подписи/шифрования инвалидирует все ранее выданные токены — пользователям потребуется перелогиниться. Планировать на окно обслуживания.
Проверка (smoke)
# 5 сегментов = JWE (шифрование включено) — норма для прода
curl -s -X POST $API/connect/token -H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=password&username=...&password=...&client_id=food-market-web&scope=api" \
| python3 -c "import sys,json;print(json.load(sys.stdin)['access_token'].count('.')+1,'сегментов')"
Проверено локально (2026-05-27): prod-режим генерирует оба сертификата в App_Data,
выдаёт 5-сегментный JWE, /api/me → 200; после рестарта сертификаты те же
(fingerprint совпадает), токен, выданный до рестарта, остаётся валиден.