Аутентификация пользователей — базовый функционал подавляющего большинства 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) для соответствующих каркасов. Обобщенных структур данных нет — каждый каркас/модуль отталкивается от "собственного представления о прекрасном". Каркасы, как правило, используют структуры с меньшим количеством атрибутов, чем модули, в силу своей большей универсальности. Зато они изначально предусматривают возможность расширения базовых структур в сторонних модулях, которые могут реализовывать альтернативные схемы аутентификации.
Ну и напоследок хотелось бы узнать, насколько сильно было мое заблуждение относительно "проектирования собственных структур данных для аутентификации пользователей".
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
staticlab
Какой смысл в этой статье?
Kolyuchkin
Смысл в подобных статьях — комментарии, «жаркие обсуждения темы».
flancer
Я пытаюсь выяснить является ли тезис "Пока же создание очередного web-приложения зачастую начинается с проектирования собственной структуры данных для аутентификации пользователей" заблуждением или нет.
Это же очевидно, разве нет?
poxvuibr
Нет, не очевидно. Насколько сильно ваш тезис является заблуждением вам хочется узнать только напоследок. Из резюме очевидно, что смысл статьи в том, чтобы провести обзор типов данных, используемых для аутентификации пользователей.
flancer
А после резюме — опрос. Весь текст перед — это вводная, раскрывающая пункты опроса. Очевидно, это не для всех очевидно, но я сделал все, что мог.
michael_vostrikov
У автора в предыдущей статье была эта фраза «Пока же создание очередного web-приложения зачастую начинается с проектирования собственной структуры данных для аутентификации пользователей». На что я заметил, что такое «проектирование» часто сводится к
composer require some-module
, которые можно найти для любого фреймворка. Не знаю, может кому-то удобнее с нуля все делать, но проще все-таки взять готовый модуль и сделать к нему свой UI для логина/регистрации.