Аутентификация пользователей — базовый функционал подавляющего большинства web-приложений. Этот функционал имплементирован с помощью различных языков программирования и поддерживается различными репозиториями ("хардкод", файлы, базы данных, LDAP, ...).

В предыдущей своей публикации я высказал смелое заблуждение "Пока же создание очередного web-приложения зачастую начинается с проектирования собственной структуры данных для аутентификации пользователей", на что мне было скинуто несколько ссылок на некоторые имплементации аутентификации (в основном — на PHP). Под катом — сравнение структур User-моделей этих имплементаций.
Казалось бы
Аутентификация — функционал, знакомый каждому web-разработчику. Самая простая структура данных для User-модели примерно такая:
- username
- password
Если данные размещаются в базе данных, то зачастую дополняются еще одним (как правило, целочисленным) атрибутом:
- id
Ну что ж, посмотрим, что предлагают web-разработчикам различные имплементации базового функционала (я не приводил различные структуры к единому виду, но суть и так понятна). DISCLAIMER: я не использовал эти модули "в бою", мои предположения основаны на рассматриваемых структурах данных — это просто мои предположения и ничего более. Если разработчики модуля в поле с именем email помещают домашний адрес пользователя, то мой выкладки однозначно введут вас в заблуждение.
Zend FW 2
Самая простая схема данных из рассмотренных:
CREATE TABLE user ( user_id INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, username VARCHAR(255) DEFAULT NULL UNIQUE, email VARCHAR(255) DEFAULT NULL UNIQUE, display_name VARCHAR(50) DEFAULT NULL, password VARCHAR(128) NOT NULL, state SMALLINT )
Минимально необходимый набор для БД (id, username, password), плюс идентификаторы "для человеков" (email, display_name), плюс код состояния пользователя (active, inactive, ...). Уникализация значений по email'ам наводит на мысль о возможности аутентификации как по username, так и по email'у.
Laravel
Schema::create('users', function (Blueprint $table) { $table->increments('id'); $table->string('name'); $table->string('email')->unique(); $table->string('password', 60); $table->rememberToken(); $table->timestamps(); });
Тоже одна из самых лаконичных схем данных. Поиск пользователя идет по "email", минимальный набор атрибутов User-модели дополнен атрибутом для имени пользователя ("name" — display name). "rememberToken()" скорее всего добавляет поддержку сохранения аутентификации для конкретного браузера ("Remember me" checkbox на аутентификационной форме). "timestamps()" предположительно добавляют даты создания и модификации отдельных записей (возможно — удаления, но маловероятно, т.к. нет атрибута состояния — state, status, etc.)
Symfony2
FriendsOfSymfony/FOSUserBundle
<?xml version="1.0" encoding="UTF-8"?> <doctrine-mapping ...> <mapped-superclass name="FOS\UserBundle\Model\User"> <field name="username" column="username" type="string" length="180" /> <field name="usernameCanonical" column="username_canonical" type="string" length="180" unique="true" /> <field name="email" column="email" type="string" length="180" /> <field name="emailCanonical" column="email_canonical" type="string" length="180" unique="true" /> <field name="enabled" column="enabled" type="boolean" /> <field name="salt" column="salt" type="string" nullable="true" /> <field name="password" column="password" type="string" /> <field name="lastLogin" column="last_login" type="datetime" nullable="true" /> <field name="confirmationToken" column="confirmation_token" type="string" length="180" unique="true" nullable="true" /> <field name="passwordRequestedAt" column="password_requested_at" type="datetime" nullable="true" /> <field name="roles" column="roles" type="array" /> </mapped-superclass> </doctrine-mapping>
Структура данных в FOSUserBundle содержит дополнительно атрибуты, поддерживающие сброс пароля пользователя и сохранение времени последней аутентификации пользователя.
Yii 2
$this->createTable('{{%user}}', [ 'id' => $this->primaryKey(), 'username' => $this->string(25)->notNull(), 'email' => $this->string(255)->notNull(), 'password_hash' => $this->string(60)->notNull(), 'auth_key' => $this->string(32)->notNull(), 'confirmation_token' => $this->string(32)->null(), 'confirmation_sent_at' => $this->integer()->null(), 'confirmed_at' => $this->integer()->null(), 'unconfirmed_email' => $this->string(255)->null(), 'recovery_token' => $this->string(32)->null(), 'recovery_sent_at' => $this->integer()->null(), 'blocked_at' => $this->integer()->null(), 'registered_from' => $this->integer()->null(), 'logged_in_from' => $this->integer()->null(), 'logged_in_at' => $this->integer()->null(), 'created_at' => $this->integer()->notNull(), 'updated_at' => $this->integer()->notNull(), ], $this->tableOptions);
Самая сложная структура данных из расмотренных. Помимо собственной аутентификации ("auth_key" — Remember-токен?) есть подтверждение email-адреса, восстановление пароля, контроль сессии ("logged_in_from" и "logged_in_at"), время создания/изменения данных о пользователе.
Django
Базовая модель данных состоит из двух классов AbstractBaseUser и AbstractUser:
class AbstractBaseUser(models.Model): password = models.CharField(_('password'), max_length=128) last_login = models.DateTimeField(_('last login'), blank=True, null=True) class AbstractUser(AbstractBaseUser, PermissionsMixin): ... username = models.CharField(_('username'), max_length=150, unique=True, ...) first_name = models.CharField(_('first name'), max_length=30, blank=True) last_name = models.CharField(_('last name'), max_length=150, blank=True) email = models.EmailField(_('email address'), blank=True) is_staff = models.BooleanField(_('staff status'), default=False, ...) is_active = models.BooleanField(_('active'), default=True, ...) date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
Тоже достаточно минимальная схема, хоть и "размазана" по двум классам. Из интересного — атрибут "is_staff", флаг допуска пользователя к админке web-приложения.
Loopback
Очень минималистичная структура данных:
{ "name": "User", "properties": { "realm": { "type": "string" }, "username": { "type": "string" }, "password": { "type": "string", "required": true }, "email": { "type": "string", "required": true }, "emailVerified": "boolean", "verificationToken": "string" }, ... }
Поддерживает верификацию email'ов пользователей и вводит дополнительный атрибут realm, позволяющий разделять пользователей по "областям" (полагаю, это имеет отношение к multitenant-архитектуре, SaaS-платформам).
Spring
Структура данных также минималистична:
private String password; private final String username; private final Set<GrantedAuthority> authorities; private final boolean accountNonExpired; private final boolean accountNonLocked; private final boolean credentialsNonExpired; private final boolean enabled;
Расширяется набором прав пользователя и флагами состояния учетной записи.
Резюме
Готовые структуры данных для аутентификации пользователей существуют как на уровне каркасов/framework'ов (Loopback, Django, Spring), так и на уровне отдельных модулей (ZF-Commons/ZfcUser, php-soft/laravel-users, FriendsOfSymfony/FOSUserBundle, dektrium/yii2-user) для соответствующих каркасов. Обобщенных структур данных нет — каждый каркас/модуль отталкивается от "собственного представления о прекрасном". Каркасы, как правило, используют структуры с меньшим количеством атрибутов, чем модули, в силу своей большей универсальности. Зато они изначально предусматривают возможность расширения базовых структур в сторонних модулях, которые могут реализовывать альтернативные схемы аутентификации.
Ну и напоследок хотелось бы узнать, насколько сильно было мое заблуждение относительно "проектирования собственных структур данных для аутентификации пользователей".
