Реализация RBAC (Role-Based Access Control) для веб-приложения
Пользователь заходит в систему и видит ровно то, что ему положено видеть, — не больше и не меньше. Звучит тривиально, пока не начинаешь считать: 12 типов пользователей, 40 разделов интерфейса, матрица разрешений на листе A3, которую нужно поддерживать в коде. RBAC — стандартный ответ на эту задачу: права назначаются не пользователям напрямую, а ролям, пользователи получают роли.
Модель данных
Минимальная схема для PostgreSQL:
CREATE TABLE roles (
id SERIAL PRIMARY KEY,
name VARCHAR(64) NOT NULL UNIQUE, -- 'admin', 'editor', 'viewer'
description TEXT
);
CREATE TABLE permissions (
id SERIAL PRIMARY KEY,
resource VARCHAR(128) NOT NULL, -- 'articles', 'users', 'reports'
action VARCHAR(64) NOT NULL, -- 'create', 'read', 'update', 'delete', 'publish'
UNIQUE (resource, action)
);
CREATE TABLE role_permissions (
role_id INT REFERENCES roles(id) ON DELETE CASCADE,
permission_id INT REFERENCES permissions(id) ON DELETE CASCADE,
PRIMARY KEY (role_id, permission_id)
);
CREATE TABLE user_roles (
user_id INT REFERENCES users(id) ON DELETE CASCADE,
role_id INT REFERENCES roles(id) ON DELETE CASCADE,
PRIMARY KEY (user_id, role_id)
);
Это классическая реализация RBAC0. Если нужны иерархические роли (admin наследует все права editor), добавляется таблица role_hierarchy с полями parent_role_id / child_role_id и рекурсивный CTE при проверке.
Проверка прав на бэкенде
Node.js + Express, middleware-подход:
// permissions.js — загружаем права из базы при старте или кешируем в Redis
async function loadUserPermissions(userId) {
const rows = await db.query(`
SELECT DISTINCT p.resource, p.action
FROM user_roles ur
JOIN role_permissions rp ON rp.role_id = ur.role_id
JOIN permissions p ON p.id = rp.permission_id
WHERE ur.user_id = $1
`, [userId]);
return new Set(rows.map(r => `${r.resource}:${r.action}`));
}
// middleware/can.js
function can(resource, action) {
return async (req, res, next) => {
const perms = await loadUserPermissions(req.user.id);
if (perms.has(`${resource}:${action}`)) {
return next();
}
res.status(403).json({ error: 'Forbidden' });
};
}
// routes
router.delete('/articles/:id', authenticate, can('articles', 'delete'), deleteArticle);
router.post('/articles', authenticate, can('articles', 'create'), createArticle);
Для PHP/Laravel паттерн аналогичен — Gate и Policy:
// AuthServiceProvider
Gate::before(function (User $user, string $ability) {
if ($user->hasRole('superadmin')) {
return true; // superadmin обходит все проверки
}
});
Gate::define('articles.delete', function (User $user) {
return $user->hasPermission('articles', 'delete');
});
// В контроллере
public function destroy(Article $article)
{
$this->authorize('articles.delete');
$article->delete();
return response()->noContent();
}
Метод hasPermission делает один запрос с JOIN или берёт из кеша — зависит от нагрузки.
Кеширование матрицы прав
На каждый HTTP-запрос гонять JOIN через три таблицы расточительно. Права пользователя меняются редко — это хорошо кешируется:
// redis cache, TTL 5 минут
async function getUserPermissions(userId) {
const cacheKey = `user_perms:${userId}`;
const cached = await redis.get(cacheKey);
if (cached) return new Set(JSON.parse(cached));
const perms = await loadUserPermissions(userId);
await redis.setex(cacheKey, 300, JSON.stringify([...perms]));
return perms;
}
// Инвалидация при изменении ролей пользователя
async function assignRole(userId, roleId) {
await db.query(
'INSERT INTO user_roles (user_id, role_id) VALUES ($1, $2) ON CONFLICT DO NOTHING',
[userId, roleId]
);
await redis.del(`user_perms:${userId}`);
}
Управление ролями в интерфейсе
Администратор должен видеть и редактировать матрицу. Минимальный API для этого:
GET /api/roles — список ролей
POST /api/roles — создать роль
GET /api/roles/:id/permissions — права роли
PUT /api/roles/:id/permissions — обновить права роли (массив permission_id)
GET /api/users/:id/roles — роли пользователя
POST /api/users/:id/roles — назначить роль
DELETE /api/users/:id/roles/:roleId — снять роль
На фронте таблица с чекбоксами resource × action — стандартный UI для такого экрана.
Что влияет на сроки
Базовая реализация (3–5 ролей, без наследования, без UI управления ролями) — 2–3 дня. Это схема, middleware, интеграция с существующей аутентификацией, тесты.
Если добавляется UI для управления ролями и правами — ещё 2 дня. Если иерархические роли или мультитенантность (роли изолированы по организации) — закладываем ещё 2–3 дня на усложнение схемы и логики проверок.







