Авторизация и контроль доступа KSAILab
Авторизация и контроль доступа KSAILab
Статус документа
Этот документ является актуальным источником истины по auth- и permission-модели платформы после перехода с Zitadel на Keycloak.
По состоянию на 2026-03-16 runtime auth foundation уже фиксирован в backend-ksailab через MR !15, где canonical human/browser contract доведен до session-based bootstrap.
Отдельно по состоянию на 2026-03-24 следующая auth wave уже зафиксирована в cross-repo issue contracts, но ещё не должна описываться как везде landed runtime:
ksailab/backend-ksailab#14— opaque browser cookie + server-side Redis-backed auth session + full platform logout;ksailab/frontend-ksailab#8— product logout UX и явное различениеlogged-out,session-expired,unauthorized;ksailab/ksailab-keycloak-scripts#9— Keycloak post-logout redirect contract для browser flow.
Это означает:
Keycloakуже является источником identity и coarse-grained realm roles;- canonical browser login entry теперь
GET /api/v1/auth/login; - canonical callback endpoint теперь
GET /api/v1/auth/callback; - canonical logout endpoint теперь
POST /api/v1/auth/logout; GET /api/v1/auth/meподнимает текущую human/browser session через backend-owned cookie;- отдельного
/capabilitiesendpoint в этой фазе нет; - admin read layer для permission administration теперь живёт отдельно в
GET /api/v1/permissions/catalog,GET /api/v1/permissions/role-mappingsиGET /api/v1/permissions/bundles; - local
POST /api/v1/auth/loginиPOST /api/v1/auth/refreshбольше не являются рабочим auth flow и остаются только как410 Gone; - onboarding orchestration уже вынесен в отдельный
POST /api/v1/onboarding/*контур; - startup/health уже показывают Keycloak connectivity и diagnostic human identity readiness;
- остаточный auth-foundation reconciliation ещё не завершён и остаётся в
ksailab/backend-ksailab#1.
Источник истины для этого раздела теперь нужно читать в двух слоях:
- landed baseline: backend-owned browser session уже существует и даёт bootstrap через
GET /api/v1/auth/me; - target next wave: browser cookie должен быть opaque session key, auth session и OIDC tokens должны жить server-side, а platform logout должен завершать не только локальную app session, но и Keycloak SSO session.
Связанные репозитории:
backend-ksailabfrontend-ksailabkeycloak-scripts-ksailabdocs-ksailab
Keycloak-контур теперь разнесён по четырём отдельным репозиториям:
infra-keycloak-ksailab— физический deployment Keycloak и связанной инфраструктуры;ci-pipelines/pipelines-ksailab— shared CI source of truth для bootstrap/smoke pipeline logic;keycloak-scripts-ksailab— realm/bootstrap/provisioning scripts и IAM docs;keycloak-theme-ksailab— отдельный repo для login theme.
Назначение
Система авторизации KSAILab должна решать сразу четыре задачи:
- централизованная аутентификация пользователей;
- централизованное управление ролями и жизненным циклом аккаунтов;
- быстрая выдача UI-прав для фронта;
- гибкая ABAC-авторизация на реальных данных платформы.
Для KSAILab этого нельзя добиться одной только ролью в токене. Поэтому модель строится из трёх слоёв:
- Identity — в
Keycloak. - Global capabilities — рассчитываются backend и отдаются фронту.
- Resource-scoped ABAC — исполняется backend по данным своей БД.
Ключевое архитектурное решение
Identity source of truth
Keycloak — единственный IAM-компонент платформы.
Он отвечает за:
- login/logout;
- пароль и credential lifecycle;
- realm roles;
- disable/enable accounts;
- reset password;
- OIDC/JWT токены;
- admin API для platform orchestration.
В текущем KsaiLab-контуре для этого задействованы клиенты:
ksailab-frontendksailab-backendksailab-admin-service
Их роли нужно трактовать без двусмысленности:
ksailab-frontend— текущий transition browser-login client в Keycloak. Он может оставаться частью runtime-совместимости, но не делает frontend владельцем canonical browser auth flow.ksailab-backend— текущий backend-facing audience client для bearer/manual сценариев и token validation contract.ksailab-admin-service— service account path для provisioning и lifecycle-операций backend.
Важно: ksailab-admin-service — это service account для provisioning и lifecycle-операций backend, а не human admin пользователь.
Первый нормальный human admin внутри realm ksailab должен создаваться отдельным Keycloak operator flow. Рекомендуемый username для этого bootstrap-пути — platform-admin.
Ownership boundaries
Ниже находится canonical разделение ответственности для human/browser auth:
| Зона | Владелец | Что именно означает |
|---|---|---|
| Identity provider | Keycloak | Логин-форма, credential lifecycle, OIDC endpoints, realm roles, token issuance |
| Canonical browser auth flow | Backend | /api/v1/auth/login, /api/v1/auth/callback, opaque browser cookie as target contract, post-login redirect |
| Access bootstrap for UI | Backend | GET /api/v1/auth/me, principal, bundles, effective_capabilities |
| Menu / route / component gating | Frontend | Потребление готового bootstrap-контракта, но не code exchange |
| Resource-scoped access decision | Backend | ABAC и повторная серверная валидация каждого реального действия |
Практическое правило:
- frontend не владеет canonical browser token exchange;
- Keycloak не владеет platform-specific effective capabilities;
- backend не должен перекладывать окончательное решение по доступу на UI.
Current baseline vs next logout/session wave
Чтобы команда не смешивала уже landed runtime и следующую wave, нужно фиксировать отдельные уровни контракта:
Current landed baseline
- backend уже владеет canonical browser login/callback/bootstrap path;
- frontend уже не должен владеть code exchange и canonical token lifecycle;
GET /api/v1/auth/meуже поднимает human/browser context из backend-owned session;- browser logout endpoint уже называется
POST /api/v1/auth/logout, но текущий runtime baseline ещё нельзя автоматически трактовать как завершённый full platform logout.
Target next wave
- browser cookie хранит только opaque session key;
- browser auth session живёт server-side и целевым storage для неё является
Redis; - access/refresh/id tokens живут только внутри server-side auth session и не считаются browser-owned state;
GET /api/v1/auth/mebootstrap-ит UI из server-side auth session, а не из frontend-managed tokens;local app logoutиfull platform logoutописываются как разные уровни;- продуктовая кнопка
Выйтив KSAILab должна означатьfull platform logout.
Что не нужно утверждать раньше времени
- что Keycloak post-logout redirect contract уже везде enforced;
- что frontend
logged-outworkaround уже убран из runtime; - что Redis-backed auth session уже landed во всех средах только потому, что она является целевым контрактом следующей wave.
Provisioning source of truth
Первичный завод аккаунтов и onboarding пользователей выполняется через backend orchestration поверх Keycloak.
Для первой версии действует такой operating model:
- аккаунты заводит только
admin; - почта и внешние invite-сервисы не используются;
- временный пароль генерируется системой;
- create-операции возвращают
credential_receipt, а не обычное inline-полеtemporary_password; - временный пароль раскрывается только через одноразовый
consumeflow; - на первом входе пользователь обязан сменить пароль.
Подробный документ по этой модели:
Authorization source of truth
Backend остаётся точкой принятия решений по доступу к платформенным ресурсам.
Причина: Keycloak не знает платформенные отношения и состояния:
- кто владеет курсом или темой;
- кто является преподавателем конкретной группы;
- кто назначен в группу или курс;
- кто соавтор темы;
- архивирован ли ресурс;
- активен ли доступ к материалу;
- какой у объекта статус жизненного цикла.
Поэтому в KSAILab:
- RBAC задаёт coarse-grained базу;
- capabilities задают доступ к экранам и действиям UI;
- ABAC окончательно решает доступ к конкретному ресурсу.
Ролевая модель
Базовые роли хранятся в Keycloak как realm roles:
adminteacherstudent
Роль не должна использоваться как единственный механизм UI-разграничения.
Она нужна для:
- coarse-grained классификации пользователя;
- базового starter mapping;
- понятной административной модели;
- упрощения части policy checks.
Capability model
Где живёт capability catalog
Каталог capabilities не должен рождаться вручную в нескольких местах сразу.
Для KSAILab фиксируется такая модель:
- capability definition живёт в backend registry;
- документация в
permission-catalog.mdявляется canonical human-readable каталогом; - platform UI не создаёт новые capability keys, а использует уже опубликованный каталог;
- Keycloak не хранит полный каталог platform permissions.
Это означает: capability key появляется только через контролируемый релиз, а не через ручную правку в IAM.
Как считается effective access
Повседневное администрирование прав выполняется в платформе, а не в Keycloak.
Backend должен рассчитывать effective access по модели:
principal + bundles + overrides
Практически это означает:
- realm role приходит из Keycloak;
- bundles, assignments и overrides живут в платформе;
- frontend получает уже рассчитанный результат через backend bootstrap endpoint;
- первой фазе достаточно только
GET /api/v1/auth/me, без отдельного/capabilitiesendpoint.
Отдельно для admin-facing permissions UX backend может отдавать read-only metadata через:
GET /api/v1/permissions/catalog;GET /api/v1/permissions/role-mappings;GET /api/v1/permissions/bundles.
Этот слой не заменяет bootstrap /auth/me и не должен трактоваться как frontend-owned source of truth.
Операционная модель вынесена отдельно:
Что хранится в Keycloak, а что в платформе
В Keycloak
- identity;
- credentials;
- login/logout endpoints и SSO session lifecycle;
- realm roles
admin,teacher,student; - clients;
- protocol mappers;
- service account
ksailab-admin-service.
В платформе
- capability definitions как product contract;
- bundles;
- assignments;
- allow/deny overrides;
- effective permission calculation;
- resource-scoped ABAC;
- browser auth session storage;
- full platform logout orchestration поверх локальной app session и Keycloak logout path.
Это ключевое разделение, без которого Keycloak быстро превратится в неудобное хранилище сотен платформенных прав.
Контракт прав для фронта
Базовый bootstrap
Фронт должен получать права не из случайных полей UI и не из ручного парсинга токена, а через backend.
Текущий canonical bootstrap-контракт:
GET /api/v1/auth/me
Runtime response уже содержит:
principalclassificationrealm_rolesbundleseffective_capabilities
Этого достаточно для:
- initial render;
- меню;
- route guards;
- component gating;
- выбора доступных onboarding/admin действий.
В этой фазе capability bootstrap должен идти только через этот endpoint. Отдельный /capabilities endpoint не реализован и не должен описываться как существующий runtime contract.
Отдельно от bootstrap backend now exposes read-only admin metadata endpoints для Permissions section:
GET /api/v1/permissions/catalog— capability registry с пометкой catalog-vs-runtime baseline;GET /api/v1/permissions/role-mappings— starter role mappings и текущий runtime baseline поrole:*;GET /api/v1/permissions/bundles— starter bundle composition.
Они нужны для Permissions -> Roles и Permissions -> Groups, но не подменяют GET /api/v1/auth/me как initial-render contract.
GET /api/v1/auth/me нужен не для прямой работы с Keycloak, а для backend-owned bootstrap текущего browser/session контекста:
- поднять current principal из server-side auth session;
- нормализовать role/classification и platform profile;
- вернуть global capabilities для menu и route gating;
- не смешивать browser session auth с explicit bearer/manual flow.
Для следующей wave это нужно читать ещё жёстче:
- browser получает только opaque session key в cookie;
- auth session и tokens остаются server-side;
/auth/meне должен интерпретироваться как чтение browser-side token storage.
Что сейчас больше не считается рабочим auth flow
Следующие endpoints не должны больше описываться как основа авторизации:
POST /api/v1/auth/loginPOST /api/v1/auth/refresh
Они оставлены только как deprecated compatibility layer и возвращают 410 Gone.
Что не должен делать фронт
Фронт не должен:
- считать наличие роли эквивалентом полного доступа;
- парсить JWT как единственный источник permissions;
- хранить бизнес-логику доступа в десятках компонентов;
- хранить canonical access/refresh tokens в
localStorage; - строить browser auth вокруг frontend-managed bearer lifecycle;
- считать скрытие кнопки окончательной проверкой доступа.
Явный bearer flow
В runtime browser auth для человека строится только через backend-owned session/cookie path.
Явный bearer flow допустим только как clearly separated non-browser/manual flow:
- bearer token должен быть выпущен настроенным
Keycloakrealm; - bearer path может использоваться для tooling, manual testing или других не-браузерных сценариев;
- bearer path не должен описываться как canonical browser contract.
Canonical Browser Flow
Для платформы принимается схема backend-owned browser session.
Canonical browser flow
- Frontend отправляет browser на
GET /api/v1/auth/login. - Backend сохраняет
stateиredirect target, затем уводит browser вKeycloak. Keycloakвозвращает browser наGET /api/v1/auth/callback.- Backend принимает
authorization code, сервер-серверно делает code exchange и валидирует token contract. - Backend создаёт browser auth session server-side и выставляет browser cookie.
- Backend редиректит browser обратно на frontend route.
- Frontend bootstrap-ится через
GET /api/v1/auth/me. - Logout идёт через
POST /api/v1/auth/logout.
Для target next wave шаг 5 нужно понимать так:
- в cookie лежит только opaque session key;
- сама auth session живёт server-side;
- target storage для неё —
Redis.
sequenceDiagram
participant Browser
participant Frontend
participant Backend
participant Keycloak
Frontend->>Backend: GET /api/v1/auth/login
Backend-->>Browser: Redirect to Keycloak
Browser->>Keycloak: Browser login flow
Keycloak-->>Browser: Redirect to /api/v1/auth/callback?code=...
Browser->>Backend: GET /api/v1/auth/callback
Backend->>Keycloak: Server-side code exchange
Keycloak-->>Backend: Tokens / claims
Backend-->>Browser: Set opaque session cookie + redirect to frontend
Frontend->>Backend: GET /api/v1/auth/me
Backend-->>Frontend: principal + bundles + effective_capabilities
Storage model for browser auth
Чтобы больше не спорить о browser/session ownership, нужно использовать такую формулировку:
- browser auth owner — backend;
- browser cookie — opaque session key, а не auth payload;
- access/refresh/id tokens — server-side only внутри backend auth session;
Redis— target storage для server-side auth session следующей wave;- frontend не должен владеть canonical token storage и не должен требовать токены для bootstrap.
Logout levels
Logout теперь нужно описывать в двух уровнях, а не одним словом:
Local app logout
- инвалидирует локальную KSAILab/backend session;
- очищает browser cookie локального приложения;
- не гарантирует завершение Keycloak SSO session;
- не должен описываться как финальный продуктовый смысл кнопки
Выйти.
Full platform logout
- инвалидирует локальную KSAILab/backend session;
- завершает связанный Keycloak/browser SSO path;
- должен приводить к тому, что следующий login снова показывает реальную страницу входа, а не silent re-login под ещё живым аккаунтом;
- является target contract для
backend-ksailab#14,frontend-ksailab#8,ksailab/ksailab-keycloak-scripts#9.
Что делает кнопка Выйти в KSAILab
Source-of-truth формулировка должна быть такой:
- продуктово кнопка
Выйтиозначаетfull platform logout; - если runtime конкретной среды ещё находится в переходном состоянии и фактически выполняет только
local app logout, это должно описываться как temporary mismatch, а не как canonical contract; - frontend
logged-outUX и Keycloak post-logout redirect contract должны читаться как companion rollout pieces, а не как отдельные конкурирующие semantics.
Почему именно так
Эта схема лучше подходит под KSAILab, чем browser-managed token flow, потому что:
- снижает зависимость UI от
localStorageтокенов; - убирает frontend-managed access/refresh lifecycle из canonical browser path;
- даёт backend полный контроль над principal bootstrap;
- позволяет централизованно считать capabilities и контекст.
Что не является canonical browser contract
Следующие трактовки считаются ошибочными:
- direct frontend-to-Keycloak login как default browser path;
- frontend-owned code exchange;
- frontend-managed access/refresh lifecycle как основа browser auth;
- трактовка
ksailab-frontendкак long-term owner всего browser auth flow.
Transition vs target client model
Transition model
Текущее переходное состояние нужно понимать так:
- canonical browser flow уже принадлежит backend;
- backend login entry и backend callback уже являются supported runtime contract;
- при этом Keycloak browser login может временно опираться на существующий client config
ksailab-frontend; - это допустимо только как transition-совместимость, а не как доказательство frontend ownership.
Target model
Архитектурный фаворит для следующего этапа:
- отдельный backend browser-auth client в Keycloak;
- явный redirect URI policy под backend callback;
- отсутствие двусмысленности между browser-session auth и explicit bearer/manual client contract;
ksailab-frontendперестаёт трактоваться как долгосрочный canonical browser-login client.
Именно эту target-модель нужно считать preferred direction для frontend-ksailab#6, keycloak-scripts-ksailab#8 и последующих runtime cleanup changes.
Provisioning и onboarding
Что уже landed в backend
Первая рабочая версия onboarding orchestration уже поддерживает:
POST /api/v1/onboarding/teachersPOST /api/v1/onboarding/groupsPOST /api/v1/onboarding/groups/{group_id}/teachersPOST /api/v1/onboarding/groups/{group_id}/students/bulkPOST /api/v1/onboarding/credentials/consume
Это покрывает три базовых сценария:
- создание teacher account;
- создание group и teacher assignment;
- bulk onboarding студентов в group.
Что важно не перепутать
teacher account creationне равноteacher assignment to group;group creationне равноstudent provisioning;credential receiptне равно обычному постоянному паролю;ksailab-admin-serviceне является human admin.
Источники трассируемости
- shared CI source-of-truth для KsaiLab живёт в
ci-pipelines/pipelines-ksailab; - backend deployment/runtime source-of-truth живёт в
infra/infra-backend-ksailab; backend-ksailab!15не должен превращаться в выдуманный infra diff: rollout review для ingress/callback/cookie semantics может потребовать отдельный follow-up в infra-репозитории, но не скрытый docs claim.
Runtime capability baseline
Текущий backend bootstrap уже возвращает минимальный набор onboarding-oriented capabilities:
auth.bootstrap.readonboarding.teacher.createonboarding.group.createonboarding.group.assign_teacheronboarding.student.bulk_createonboarding.credentials.consume
Это не отменяет будущий полный capability catalog, но уже является действующим runtime-контрактом для первой волны фронтовой интеграции.
ABAC в KSAILab
Принцип
ABAC нужен там, где одного capability недостаточно.
Например, наличие education.courses.manage ещё не означает, что преподаватель может менять любой курс. Нужно проверить отношения пользователя к конкретному ресурсу.
Формула проверки
Любая защищённая операция должна раскладываться так:
- principal аутентифицирован;
- у principal есть нужный global capability;
- resource-scoped правило пропускает конкретный объект.
Backend остаётся единственным местом, где эта проверка считается окончательной.
Как быстро добавлять и убирать доступы
Добавить новую фичу
Целевой сценарий для новой фичи такой:
- Разработчик добавляет capability definition в backend registry.
- Документация обновляет
permission-catalog.md. - Backend получает policy check.
- Frontend получает новый guard или menu binding.
- После релиза администратор добавляет capability в нужные bundles через platform UI.
Это и есть целевая модель релиз + UI-настройка, которая даёт скорость без потери управляемости.
Быстро изменить доступ существующей фичи
Для уже опубликованной capability релиз не нужен, если требуется:
- выдать или снять bundle;
- добавить capability в существующий bundle;
- убрать capability из bundle;
- назначить override конкретному пользователю;
- отключить или включить пользователя.
Как быстро настраивать Keycloak
Базовый принцип
Keycloak должен конфигурироваться как Realm as Code.
Ручная настройка через GUI допустима для диагностики или аварийного unblock, но не должна быть единственным источником истины.
Что должно быть описано as code
- realm;
- clients
ksailab-frontend,ksailab-backend,ksailab-admin-service, а после выделения target browser client — и отдельный backend browser-auth client; - redirect URIs;
- logout URIs и post-logout redirect contract;
- realm roles;
- protocol mappers;
- service account permissions для backend orchestration.
Где именно живёт этот as-code контур:
- realm/bootstrap/provisioning scripts и IAM docs — в
keycloak-scripts-ksailab; - shared pipeline logic — в
ci-pipelines/pipelines-ksailab; - deployment/ingress/runtime delivery для самого Keycloak — в
infra-keycloak-ksailab; - login theme и branding — в
keycloak-theme-ksailab.
Поддерживаемый scripting/ops path для scripts repo теперь только Python. PowerShell fallback больше не должен описываться как supported path.
Что нельзя объявлять завершённым
Сейчас нельзя писать, что:
- первый human admin уже полностью bootstrap-ится через чистый Keycloak-first flow;
- startup log про локального admin доказывает существование готового Keycloak-admin пользователя;
ksailab/backend-ksailab#1закрыт без остатка.
Корректная формулировка: startup/health теперь показывают connectivity и diagnostic human identity readiness, но не заменяют отдельный operator flow для первого human admin.
Validation и rollout implications
Эта auth-модель уже опирается на реальные backend-проверки:
- integration tests на
GET /api/v1/auth/me; - tests на deprecated
410 Goneдля local auth endpoints; - integration tests на missing/expired opaque session ->
401для/api/v1/auth/me; - integration tests на local app logout vs full platform logout semantics по мере реализации next wave;
- onboarding tests на teacher/group/student orchestration;
- tests на receipt-based consume flow;
- health/startup diagnostics по
Keycloak issuerиJWKS.
Фронтовая миграция guards, menu gating и onboarding UI остаётся отдельной задачей во frontend-ksailab.