Идентификация и авторизация

Подключите существующую систему аутентификации и авторизации через Middleware Transport, Endpoint и Service.

Univer Collaboration SDK не определяет пользователей, роли и модель ACL. Приложение устанавливает доверенную идентичность в Middleware Transport, затем защищает Sessions реального времени и эталонные данные совместной работы через Middleware Endpoint и Service.

Эта страница — практическое применение Middleware и Events для интеграции идентификации и авторизации через Middleware.

Проверка прав

Остановите предыдущий пример и выполните pnpm example:permissions. Откройте http://127.0.0.1:3010 в двух независимых контекстах с пользователями Alice · Editor и Bob · Viewer. Первый может редактировать, второй видит синхронизированные результаты без возможности изменения. В server/main.ts проверьте transport.use(), readUnitData, joinUnit, submitChangeset. Замените демонстрационные Cookie и список пользователей своей аутентификацией и ACL документов. Режима чтения на frontend недостаточно.

Также доступен Casey · No access. Для этой учётной записи загрузка документа должна быть запрещена: запрос snapshot возвращает HTTP 403.

Модель идентификации

ИдентификаторИсточникЗначение и жизненный цикл
userIDПриложениеСтабильная бизнес-идентичность и автор подтверждённых наборов изменений
memberIDEndpointID участника в сети для одной Session WebSocket; меняется после переподключения
sid + reqIdКлиентИдентификатор отправки набора изменений, сохраняющийся при повторах

Поля профиля из браузера, memberID и ревизии не заменяют аутентификацию приложения. Сопоставьте стабильный ключ пользователя с context.userID.

Установление идентичности в Middleware Transport

Transport заново выполняет Middleware для каждого HTTP-запроса совместной работы. Приложение может прочитать Cookie, Session или Bearer-токен и добавить результат аутентификации в Context:

TypeScript
transport.use(async (context, next) => {  const user = await auth.requireUser(context.incomingMessage);  context.userID = user.id;  context.customData.user = user;  context.customData.tenantID = user.tenantID;  context.customData.traceID = readTraceID(context.incomingMessage);  await next();});

При неудачной аутентификации завершите HTTP-ответ, не вызывая next():

TypeScript
transport.use(async (context, next) => {  const user = await auth.findUser(context.incomingMessage);  if (!user) {    context.response.statusCode = 401;    context.response.end("Authentication required");    return;  }  context.userID = user.id;  await next();});

SDK не предписывает имена Cookie, форматы токенов, таблицы пользователей и процессы входа.

Передача идентичности в Session WebSocket

Путь WebSocket не доверяет пользовательским полям в данных клиента. Endpoint переносит аутентифицированный HTTP Context в Session реального времени через одноразовый Session Ticket:

text
HTTP-запрос Session Ticket  → аутентификация в Middleware Transport  → Endpoint сохраняет { userID, customData }  → возвращает непрозрачный одноразовый билет  → открытие WebSocket погашает билет  → создаётся Session { userID, memberID, customData }

Строка билета не содержит userID или customData. memberID идентифицирует только текущее соединение и не является самостоятельным удостоверением личности. При общем хранилище билетов в развёртывании на нескольких узлах сохранённый customData сериализуется, поэтому он должен быть сериализуем в JSON.

Авторизация на правильной границе

Защищаемое действиеMiddleware
HTTP-вход совместной работыTransport use()
Соединение WebSocketEndpoint connect
Вход в Room UnitEndpoint joinUnit
Чтение снимков, блоков и наборов измененийService readUnitData
Отправка правок содержимогоService submitChangeset
Создание UnitService createUnit
Удаление UnitsService deleteUnits
Восстановление UnitsService recoverUnits

joinUnit в Endpoint управляет только входом Session в Room реального времени. Снимки и недостающие наборы изменений можно читать напрямую по HTTP, поэтому проверка JOIN не заменяет readUnitData. Аналогично, интерфейс клиента «только чтение» — подсказка продукта, а не замена серверной политике submitChangeset.

Защита чтения, JOIN и редактирования

TypeScript
import { CollabError } from "@univerjs-pro/collaboration-service";endpoint.use("joinUnit", async (context, next) => {  const allowed = await acl.canRead(context.session.userID, context.unitID);  if (!allowed) {    throw new CollabError("PERMISSION_DENIED", "Cannot join this Unit");  }  await next();});service.use("readUnitData", async (context, next) => {  const allowed = await acl.canRead(context.userID, context.request.unitID);  if (!allowed) {    throw new CollabError("PERMISSION_DENIED", "Unit is not accessible");  }  await next();});service.use("submitChangeset", async (context, next) => {  const unitID = context.request.changeset.unitID;  const allowed = await acl.canEdit(context.userID, unitID);  if (!allowed) {    throw new CollabError("PERMISSION_DENIED", "Unit is read-only");  }  await next();});

Реальные приложения обычно устанавливают все три правила: чтение для HTTP и JOIN и редактирование для отправки.

Защита жизненного цикла Unit

Протокол совместной работы открывает существующие Units. «Создать документ» — обычно API приложения, который создаёт запись продукта и ACL до вызова createUnitFromData() или createUnitFromSnapshot(). Защищайте жизненный цикл также через Middleware Service:

TypeScript
service.use("createUnit", async (context, next) => {  if (!(await acl.canCreate(context.userID, context.request.snapshot.type))) {    throw new CollabError("PERMISSION_DENIED", "Unit creation denied");  }  await next();});service.use("deleteUnits", async (context, next) => {  for (const unitID of context.request.unitIDs) {    if (!(await acl.canDelete(context.userID, unitID))) {      throw new CollabError("PERMISSION_DENIED", `Cannot delete ${unitID}`);    }  }  await next();});

Защитите recoverUnits отдельно. Пользователь, ранее имевший право удаления, не обязательно навсегда сохраняет право восстановления.

Повторное использование результатов через customData

Middleware в одном вызове Service могут использовать customData, чтобы не повторять запросы:

TypeScript
service.use("submitChangeset", async (context, next) => {  const unitID = context.request.changeset.unitID;  context.customData.role ??= await acl.getRole(context.userID, unitID);  if (context.customData.role === "viewer") {    throw new CollabError("PERMISSION_DENIED", "Unit is read-only");  }  await next();});

customData относится только к текущему вызову или Session и не сохраняется автоматически. Роли, ACL и связи арендаторов остаются в хранилище приложения.

Отдельные политики для расширений

History, Thread Comment и Worktree имеют независимые Middleware Service:

  • список History и чтение изменений должны проверять право чтения Unit;
  • список комментариев должен проверять чтение, а добавление, ответы, редактирование и удаление — политику комментариев;
  • Worktree должен отдельно защищать видимость, правки черновиков, смену статуса и слияние;
  • слияние Worktree в основную ветку по-прежнему проходит через Middleware Service основной ветки.

Правила основного Collaboration Service не защищают эти модули автоматически. Повторно используйте один сервис политик приложения, а не отдельную модель ACL для каждого расширения.

Настройка текущего пользователя в клиенте

UserManagerService.setCurrentUser() задаёт ID, имя и аватар пользователя для текущего экземпляра Univer, чтобы их могли читать компоненты интерфейса. Например, фильтр «Касается меня» панели комментариев использует этот ID для показа комментариев, связанных с текущим пользователем.

Задайте его после создания экземпляра Univer и до загрузки документа:

TypeScript
import { UserManagerService } from "@univerjs/core";// currentUser is your application profile; id/name/avatar are example field names.univer  .__getInjector()  .get(UserManagerService)  .setCurrentUser({    userID: currentUser.id,    name: currentUser.name,    avatar: currentUser.avatar ?? "",  });

Сопоставьте стабильный ID пользователя приложения с userID, совпадающим с аутентифицированным context.userID сервера. Имена и аватары участников, передаваемые при совместной работе, задаются в серверном Middleware connect, как описано в Presence.

Полная запускаемая реализация приведена в примере прав доступа.

Насколько полезно это руководство?

© 2026 DreamNum Co., Ltd.