Аутентификация пользователей — базовый функционал подавляющего большинства 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) для соответствующих каркасов. Обобщенных структур данных нет — каждый каркас/модуль отталкивается от "собственного представления о прекрасном". Каркасы, как правило, используют структуры с меньшим количеством атрибутов, чем модули, в силу своей большей универсальности. Зато они изначально предусматривают возможность расширения базовых структур в сторонних модулях, которые могут реализовывать альтернативные схемы аутентификации.
Ну и напоследок хотелось бы узнать, насколько сильно было мое заблуждение относительно "проектирования собственных структур данных для аутентификации пользователей".