8.8 KiB
8.8 KiB
SSO / Auth — исследование и контекст
Дата: 2026-05-30
Актуально для веткиsonnet
Исходные данные от команды
Ссылки (получены 2026-05-30)
| Что | URL | Примечание |
|---|---|---|
| Пример SSO на ColdFusion | https://gitea.services.ngcloud.ru/smishchuk/payg-report.git | Написан «на коленке», но рабочий |
| Конфигурация сервиса в Deck | https://deck.ngcloud.ru/services/instance/detail/523413d2-1ffd-457b-b0e3-0a8185fe9358 | Там видно как кладутся env-переменные |
Файлы из payg-report сохранены локально
research/payg-sso/
Application.cfc — основной SSO flow (редирект, обмен code→token, refresh, logout)
lib_oauth2.cfc — Authorization Code Flow клиент
lib_jwt.cfc — валидация JWT RS256
Keycloak — эндпоинты
Realm: cloud
Base: https://keycloak.nubes.ru/realms/cloud
| Назначение | URL |
|---|---|
| Авторизация (браузер → KK) | https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/auth |
| Обмен code → token | https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/token |
| Logout | https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/logout |
| JWKS (публичные ключи) | https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/certs |
| OpenID конфигурация | https://keycloak.nubes.ru/realms/cloud/.well-known/openid-configuration |
Реальная схема аутентификации
Браузер
│
│ 1. GET / (без токена)
▼
наше приложение
│
│ 2. redirect → KK /auth?client_id=...&redirect_uri=...&scope=openid
▼
Keycloak (keycloak.nubes.ru, realm=cloud)
│
│ 3. Пользователь логинится
│ 4. KK redirect → наш /callback?code=...
▼
наше приложение
│
│ 5. POST /token (code + client_secret) → access_token + refresh_token
│ 6. Валидация JWT через JWKS (certs endpoint)
│ 7. Извлечение claims из JWT → req.user
▼
Работаем: req.user.ClientID = "WZ01325" и тд
Важно из примера payg-report:
- Тип flow: Authorization Code Flow (не Implicit, не PKCE — обычный)
client_secretпередаётся при обмене code→token (confidential client)- Сертификат берётся с JWKS endpoint при старте, кэшируется в application scope
- При истечении части времени жизни — автоматический refresh через refresh_token
JWT — структура токена
JWT выпускает auth-api (не Keycloak напрямую).
Алгоритм: RS256.
JWKS для валидации: из auth-api (уточнить URL у DevOps).
⚠️ В payg-report используют
keycloak.nubes.ru/certs, но наш портал deck.ngcloud.ru
идёт через auth-api.ngcloud.ru — нужно уточнить чей именно JWT будет у нас.
Claims в JWT (из реального токена, secrets.txt)
| Claim | Тип | Пример | Назначение |
|---|---|---|---|
iss |
string | "auth-api" |
Издатель |
sub |
string | "0199e325-1cdf-..." |
UUID пользователя |
exp |
number | 1795627443 |
Срок действия |
iat |
number | 1780075443 |
Выпущен |
ClientID |
string | "WZ01325" |
WZ-номер компании ← ключевой claim |
company_id |
string (UUID) | "3e64aac6-dcfc-..." |
UUID компании |
company_name |
string | "Тест" |
Название компании |
email |
string | "tazet@narod.ru" |
Email (для аудита) |
login |
string | "tazet@narod.ru" |
Логин |
firstname |
string | "Наиль" |
Имя |
lastname |
string | "Тазетдинов" |
Фамилия |
token_type |
string | "tech" |
Тип токена |
Пример декодированного payload (из secrets.txt — не секрет, payload JWT не зашифрован):
{
"iss": "auth-api",
"sub": "0199e325-1cdf-7cda-9319-e5302a85e291",
"ClientID": "WZ01325",
"company_id": "3e64aac6-dcfc-4082-88dc-da19c86555a5",
"company_name": "Тест",
"email": "tazet@narod.ru",
"login": "tazet@narod.ru",
"token_type": "tech"
}
Конфигурация сервиса (env-переменные)
Из примера payg-report и Deck — конфиги кладутся в переменные окружения:
# Нужно получить от DevOps:
KC_CLIENT_ID=white.nodejsk8s.dev.nubes.ru # имя нашего клиента в KK (уточнить)
KC_CLIENT_SECRET=<секрет> # из Deck / у DevOps
JWKS_URL=https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/certs
# ИЛИ если через auth-api:
# JWKS_URL=https://auth-api.ngcloud.ru/.well-known/jwks.json
# В payg-report переменная называется:
IDP_CLIENT_SECRET=<секрет>
Что сейчас в нашем коде
Файл: src/auth.js
| Режим | Условие | Поведение |
|---|---|---|
| DEV_MODE | DEV_MODE=true и не production |
Авторизация пропущена, юзер захардкожен из MOCK_USERS |
| JWKS_URL задан | JWKS_URL != '' |
Валидирует Bearer-токен из заголовка через JWKS |
| Мок RS256 | Ничего из выше | Генерирует собственную RSA-пару, выдаёт и валидирует JWT сам |
Чего не хватает для продакшена:
- Authorization Code Flow (редирект на KK и обратно)
- Обмен
code→access_tokenчерез/token - Session/cookie для хранения токена между запросами
- Refresh токена
Пример SSO flow из payg-report (ключевые строки)
// Настройка (из окружения):
const client_id = process.env.IDP_CLIENT_SECRET; // их название
const auth_endpoint = 'https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/auth';
const token_endpoint = 'https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/token';
const certs_url = 'https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/certs';
// 1. Нет сессии → redirect на KK:
location(idp.buildRedirectToAuthURL({scope: 'openid profile email', state: guid}));
// 2. KK вернул ?code=... → меняем на токен:
const resp = idp.makeAccessTokenRequest(url.code);
// 3. Парсим JWT из ответа:
const token_data = jwt.decode(resp.access_token, idpCertificate, 'RS256');
session.auth.wz = token_data.ClientID; // WZ-номер компании
session.auth.login = token_data.preferred_username;
// 4. Refresh когда прошло > 2/3 времени жизни (только на GET):
if (timeSinceIat > refresh_expires_in / 1.5 && method === 'GET') {
const resp = idp.refreshAccessTokenRequest(session.auth.refresh_token);
// ... обновляем сессию
}
Открытые вопросы (нужен ответ от DevOps/KK-команды)
| # | Вопрос | Влияет на |
|---|---|---|
| 1 | Какой client_id выдан нашему сервису в KK realm cloud? |
Конфиг |
| 2 | Где взять client_secret? |
Конфиг |
| 3 | Токен от KK или от auth-api? Соответственно — какой JWKS_URL? | src/auth.js |
| 4 | Claim для admin-роли: чек-бокс — это realm_roles, resource_access, attribute? |
src/auth.js requireAdmin |
| 5 | ClientID в токене — строка или массив (multi-company)? |
src/queries.js, UI |
| 6 | /export — нужна ли авторизация или только сетевое ограничение по IP? |
src/routes/export.js |
API Gateway (для справки)
Из docs/auth-architecture.md:
- Gateway:
lk-api-gateway.ngcloud.ru - JWT из localStorage портала:
authApiTokens.access_token - DDOS-Guard cookies обязательны при curl-тестировании:
__ddg1_,__ddg8_,__ddg9_,__ddg10_
# Пример рабочего curl (2026-05-29):
curl -H "Authorization: Bearer <JWT>" \
-H "Origin: https://deck.ngcloud.ru" \
-H "Cookie: __ddg1_=...; __ddg8_=...; __ddg9_=...; __ddg10_=..." \
"https://lk-api-gateway.ngcloud.ru/api/v1/..."