Сейчас вы находитесь на половине пути, соединяющим браузер с Реальными Людьми™. Сессии предоставляют нам механизм хранения данных между отдельными запросами. Вторая часть уравнения — использование сессии для авторизации пользователя. Конечно, мы не можем просто доверять, что они те, кем представляются. Следовательно, нам потребуется механизм аутентификации.
Django предоставляет инструменты для решения этой частой задачи (и для многих других). Система аутентификации пользователей Django управляет аккаунтами пользователей, группами, правами и пользовательскими сессиями (с помощью cookie). Эту систему часто называют системой аутентификации и авторизации (auth/auth или система АА). Двойное имя означает, что опознавание пользователей часто состоит из двух шагов. Нам необходимо:
Проверить (аутентифицировать), кто есть этот пользователь (обычно, с помощью проверки имени и пароля по базе данных пользователей).
Проверить, что пользователь авторизован для выполнения определённых действия (обычно, с помощью проверки по таблице прав).
Согласно этим нуждам система АА состоит из ряда частей:
Пользователи: Люди, зарегистрированные на вашем сайте.
Права: Бинарные (да/нет) флаги, определяющие может данный пользователь выполнять определённую задачу.
Группы: Общий способ применения меток и прав на группу пользователей.
Сообщения: Простой способ организации очереди и отображения системных сообщений пользователям.
Профайлы: Механизм добавления в объект пользователя определённых полей.
Если вы пользовались интерфейсом администратора (описанном в главе «Интерфейс администратора Django», вы видели многие из этих инструментов. Если вы управляли пользователями и группами через интерфейс администратора, в действительности вы управляли данными в таблицах системы аутентификации.
Подобно среде управления сессиями, поддержка аутентификации поставляется в виде Django приложения в django.contrib, которое необходимо установить. Как и раньше, система АА уже установлена по умолчанию, но если вы её отключали, то необходимо выполнить следующие шаги:
Следует проверить, что среда управления сессиями установлена, как описано ранее в этой главе, так как от неё зависит работоспособность системы аутентификации.
Добавьте в параметр INSTALLED_APPS значение django.contrib.auth и запустите manage.py syncdb.
Проверьте, что в параметр MIDDLEWARE_CLASSES добавлено значение django.contrib.auth.middleware.AuthenticationMiddleware после SessionMiddleware.
После выполнения установки мы готовы работать с пользователями в функциях представлений. Главным интерфейсом, который вы будете использовать для управления пользователями, является request.user — это объект, который представляет собой текущего авторизованного пользователя. Если пользователь не авторизован, то вместо этого объекта будет AnonymousUser (подробности будут дальше).
Определить авторизовался ли пользователь можно с помощью
метода is_authenticated():
if request.user.is_authenticated():
# Пользователь авторизован.
else:
# Анонимный пользователь.
Как только вы получили объект User — чаще
всего от request.user, но возможно и с помощью одного из ранее описанных методов — у вас появился
доступ к полям и методам этого объекта. Объекты
AnonymousUser эмулируют
часть этого интерфейса, но не всю,
поэтому вы всегда должны проверять авторизацию пользователя с
помощью метода is_authenticated() перед
работой с методами объекта. В таблице «
Поля объектов User
» приведены поля, а в
таблице «
Методы объектов User
» — методы
объектов User:
Таблица 12.3. Поля объектов User
| Поле | Описание |
|---|---|
| username | Логин. Обязательное поле. Максимум 30 символов. Можно использовать только буквы, цифры и символ подчёркивания. |
| first_name | Имя пользователя. Необязательное поле. Максимум 30 символов. |
| last_name | Фамилия пользователя. Необязательное поле. Максимум 30 символов. |
| Электронная почта. Необязательное поле. | |
| password | Пароль. Обязательное поле. Хэш и метаданные пароля (Django не хранит пароль в открытом виде). За подробностями обращайтесь к секции «Пароли» ранее. |
| is_staff | Определяет входит ли пользователь в привелигерованную группу. Булево значение. |
| is_active | Определяет, активен ли данный аккаунт, т.е. можно ли им пользоваться. Булево значение. Установите в False для блокировки аккаунта. |
| is_superuser | Определяет входит ли пользователь в группу администраторов. Булево значение. |
| last_login | Дата и время последней аутентификации. По умолчанию устанавливается текущее время. |
| date_joined | Дата и время создания аккаунта. По умолчанию указывается дата и время создания аккаунта. |
Таблица 12.4. Методы объектов User
| Поле | Описание |
|---|---|
| is_authenticated() | Всегда возвращает True для «настоящих» объектов User. Метод определяет был ли текущий пользователь авторизован. Метод не назначает никаких прав и не проверяет активность пользователя. Он просто является индикатором аутентификации пользователя. |
| is_anonymous() | Возвращает True только для
объектов AnonymousUser (и
False для «настоящих»
объектов User). В общем, лучше
использовать метод
is_authenticated(). |
| get_full_name() | Возвращает значения first_name и last_name с пробелом между ними. |
| set_password(passwd) | Назначает пароль для пользователя, переданный в виде строки. Метод шифрует пароль. Метод не сохраняет объект User. |
| check_password(passwd) | Возвращает True при совпадении переданного пароля с имеющимся. |
| get_group_permissions() | Возвращает в виде строк список прав, которыми обладает пользователь благодаря вхождению в группы. |
| get_all_permissions() | Возвращает в виде строк список всех прав, которыми обладает пользователь. |
| has_perm(perm) | Возвращает True, если пользователь обладает указанным правом, которое представлено в формате «package.codename». Если пользователь неактивен, метод будет возвращать False. |
| has_perms(perm_list) | Возвращает True, если пользователь обладает всеми правами, указанными в переданном списке. Если пользователь неактивен, метод будет возвращать False. |
| has_module_perms(app_label) | Возвращает True, если пользователь обладает любым правом из переданного app_label. Если пользователь неактивен, метод будет возвращать False. |
| get_and_delete_messages() | Возвращает список объектов Message из пользовательской очереди и очищает очередь. |
| email_user(subj, msg) | Отправляет сообщение пользователю. Сообщение отсылается от значения, указанного в параметре DEFAULT_FROM_EMAIL. Вы можете также передать третий аргумент, from_email, для указания другого адреса электронной почты. |
| get_profile() | Возвращает профайл пользователя. Обратитесь к разделу «Профайлы» для подробностей. |
Наконец, объекты User имеют поля «многие-ко-многим»: groups и permissions. Объекты User могут взаимодействовать с другими объектами тем же способом, что и другие поля с такими отношениями:
# Определим группу для пользователя:
myuser.groups = group_list
# Добавим пользователя в несколько других групп:
myuser.groups.add(group1, group2,...)
# Удалим пользователя из этих групп:
myuser.groups.remove(group1, group2,...)
# Удалим пользователя из всех групп:
myuser.groups.clear()
# С правами работаем аналогично:
myuser.permissions = permission_list
myuser.permissions.add(permission1, permission2, ...)
myuser.permissions.remove(permission1, permission2, ...)
myuser.permissions.clear()
Django предоставляет встроенные функции представления для
работы с авторизацией и выходом пользователя (а так же ещё для
некоторых вещей), но прежде чем мы начнём их использовать,
рассмотрим «вручную» как пользователи
авторизуются и выходят. Django предоставляет в
django.contrib.auth две функции для выполнения
таких действий: authenticate() и
login().
Для аутентификации пользователя по переданному логину и паролю
следует использовать функцию
authenticate(). Она принимает два
именованных аргумента, username и
password, и возвращает объект
User, если пароль соответствует логину. В
противном случае функция возвращает None:
>>> from django.contrib import auth
>>> user = auth.authenticate(username='john', password='secret')
>>> if user is not None:
... print "Правильно!"
... else:
... print "Ой, что-то пошло не так!"
Функция authenticate() только проверяет
удостоверение. Для авторизации следует использовать функцию
login(). Она принимает объекты
HttpRequest и
User, сохраняет идентификатор
пользователя в сессии, используя соответствующий механизм.
Этот пример показывает как можно совместно использовать эти функции в представлении:
from django.contrib import auth
def login(request):
username = request.POST['username']
password = request.POST['password']
user = auth.authenticate(username=username, password=password)
if user is not None and user.is_active:
# Правильный пароль и пользователь "активен"
auth.login(request, user)
# Перенаправление на "правильную" страницу
return HttpResponseRedirect("/account/loggedin/")
else:
# Отображение страницы с ошибкой
return HttpResponseRedirect("/account/invalid/")
Для выхода пользователя следует использовать функцию
logout(). Она принимает объект
HttpRequest и не возвращает ничего:
from django.contrib import auth
def logout(request):
auth.logout(request)
# Перенаправление на страницу.
return HttpResponseRedirect("/account/loggedout/")
Следует отметить, что функция logout() не
вызывает исключений, если пользователь не был авторизован.
На практике вам обычно не понадобится писать свои функции авторизации и выхода, так как система АА поставляется с набором представления для общей обработки этих процессов.
Первым шагом для использования представлений аутентификации будет их привязка к URL:
from django.contrib.auth.views import login, logout
urlpatterns = patterns('',
# существующие шаблоны располагаются здесь...
(r'^accounts/login/$', login),
(r'^accounts/logout/$', logout),
)
URL /accounts/login/ и /accounts/logout/ являются стандартными для этих представлений.
По умолчанию, представление login использует
шаблон registration/login.html (вы можете
указать на другой шаблон, передав дополнительный аргумент в
представление, template_name). Эта форма нужна
для отображения полей username и
password. Простой шаблон может выглядеть так:
{% extends "base.html" %}
{% block content %}
{% if form.errors %}
<p class="error">Сожалеем, вы неправильно ввели логин или пароль</p>
{% endif %}
<form action='.' method='post'>
<label for="username">Логин:</label>
<input type="text" name="username" value="" id="username">
<label for="password">Пароль:</label>
<input type="password" name="password" value="" id="password">
<input type="submit" value="login" />
<input type="hidden" name="next" value="{{ next|escape }}" />
</form>
{% endblock %}
Если пользователь ввёл всё правильно, он перенаправляется по
умолчанию на страницу /accounts/profile/. Вы
можете переопределить это поведение, добавив скрытое поле с
именем next к URL для перенаправления на другую
страницу. Вы также можете передать это значение в качестве
параметра GET запроса в представление авторизации и оно будет
автоматически добавлено к контексту в виде переменной
next, которую вы можете вставить в скрытое
поле.
Представление для выхода работает немного по-другому. По
умолчанию, оно использует шаблон
registration/logged_out.html (которое
обычно содержит сообщение «Вы успешно вышли с сайта»). Однако, вы можете вызвать представление с
дополнительным аргументом, next_page, который
укажет представлению на какую страницу следует перейти после
выхода.
Причиной же, ради которой мы прорываемся сквозь лес проблем, является необходимость ограничения доступа к разделам нашего сайта.
Простой и прямолинейный способ ограничения доступа к страницам — проверить результат выполнения
request.user.is_authenticated() и
перенаправить на страницу аутентификации:
from django.http import HttpResponseRedirect
def my_view(request):
if not request.user.is_authenticated():
return HttpResponseRedirect('/login/?next=%s' % request.path)
# ...
или, возможно, отобразить сообщение об ошибке:
def my_view(request):
if not request.user.is_authenticated():
return render_to_response('myapp/login_error.html')
# ...
Для сокращения вы можете использовать удобный декоратор login_required:
from django.contrib.auth.decorators import login_required
@login_required
def my_view(request):
# ...
Декоратор делает следующее:
Если пользователь не авторизован, перенаправить его на /accounts/login/, передав текущий URL в строке запроса как параметр next. Например: /accounts/login/?next=/polls/3/.
Если пользователь авторизован, отобразить представление. Код функции представления предполагает в этом случае, что пользователь авторизован.
Процедура ограничения доступа, основанная на наличии определённых прав или на какой-дибо другой проверке, или на перенаправлении на другой раздел сайта для авторизации, работает таким же образом (см. предыдущий раздел).
Прямолинейным способом является проверка request.user в представлении. Например, нижеприведённое представление проверяет авторизован ли пользователь и обладает ли он правом polls.can_vote (о правах будет рассказано ниже):
def vote(request):
if request.user.is_authenticated() and request.user.has_perm('polls.can_vote')):
# Голосуем здесь
else:
return HttpResponse("Вы не можете голосовать по этому списку избирателей.")
И снова Django предоставляет сокращение user_passes_test. Оно принимает аргументы и создаёт специальный декоратор для вашей частной задачи:
def user_can_vote(user):
return user.is_authenticated() and user.has_perm("polls.can_vote")
@user_passes_test(user_can_vote, login_url="/login/")
def vote(request):
# Здесь мы точно уверены, что пользователь авторизован и имеет
# необходимы права.
...
Сокращение user_passes_test принимает один обязательный аргумент: функцию, которая принимает объект User и возвращает True, если пользователь успешно прошёл проверку. Следует отметить, что user_passes_test не производит автоматически проверку авторизации пользователя, вам следует сделать это самостоятельно.
В этом примере мы показали второй необязательный аргумент login_url, который позволяет указать URL для страницы аутентификации (по умолчанию, /accounts/login/).
Так как задача проверки прав пользователя является общей,
Django предоставляет сокращение и для этого случая: декоратор
permission_required(). Используя этот
декоратор, перепишем предыдущий пример так:
from django.contrib.auth.decorators import permission_required
@permission_required('polls.can_vote', login_url="/login/")
def vote(request):
# ...
Следует отметить, что
permission_required() также принимает
необязательный параметр login_url, который
также, по умолчанию, указывает на
/accounts/login/.
Ограничение доступа к базовым представлениям
Одним из самых популярных вопросов по Django является ограничение доступа к функциям представления. Для реализации такого ограничения вам потребуется написать промежуточный слой для представления и привязать слой к URL, чтобы вся работа шла через него:
from django.contrib.auth.decorators import login_required
from django.views.generic.date_based import object_detail
@login_required
def limited_object_detail(*args, **kwargs):
return object_detail(*args, **kwargs)
Естественно, вы можете заменить login_required на любой другой декоратор.
Самым простым способом управления системой аутентификации является интерфейс администратора. В главе «Интерфейс администратора Django» рассматривалось как управлять пользователями, их правами и доступом, большую часть времени для этого использовался интерфейс администратора.
Тем не менее, существуют API, которые могут предоставить вам абсолютный контроль над пользователями. Мы рассмотрим их далее.
Создать пользователя можно с помощью функции
create_user():
>>> from django.contrib.auth.models import User
>>> user = User.objects.create_user(username='john',
... email='jlennon@beatles.com',
... password='glass onion')
В данном случае user является экземпляром
класса User, готовым к хранению в
базе данных. Функция create_user() не
вызывает save(). Вы можете изменять
атрибуты экземпляра до его сохранения:
>>> user.is_staff = True
>>> user.save()
Изменить пароль пользователя можно с помощью функции
set_password():
>>> user = User.objects.get(username='john')
>>> user.set_password('goo goo goo joob')
>>> user.save()
Не устанавливайте атрибут password напрямую, если не знаете, что делаете. Пароль хранится в виде подсоленного хэша FIXME (т.е. хэш с префиксом) и следовательно его просто так не изменить.
Атрибут password объекта
User является строкой формата:
hashtype$salt$hash
Здесь представлены тип хэша, соль и сам хэш, разделённые символами доллара.
Тип хэша (hashtype) может быть либо sha1 (по умолчанию), либо md5 и определяет односторонний алгоритм, который используется для создания хэша для пароля. Соль (salt) является случайной строкой, используемой для подсаливания пароля при создании хэша, например:
sha1$a1976$a36cc8cbf81742a8fb52e221aaeab48ed7f58ab4
Функции User.set_password() и
User.check_password() умеют
устанавливать и проверять эти значения.
Неужели «подсоленный хэш» имеет отношение к наркоте?
Нет, подсоленный хэш не имеет ничего общего с марихуаной. На самом деле это общий метод безопасного хранения паролей. Хэш — это односторонняя криптографическая функция. Такую функцию просто вычислить, но практически невозможно получить обратно из хэша оригинальное значение.
Если бы мы хранили пароли в чистом виде, т.е. как текст, то любой, получивший доступ к базе паролей, мог бы их прочитать. Хранение паролей в виде хэша затрудняет получение паролей из украденной базы данных.
Однако, атакующий может применить к базе паролей методику перебора паролей, организовав хэширование миллионов паролей и сравнивая их со значениями, хранящимися в базе. Это займёт некоторое время, но гораздо меньшее, чем вы можете подумать — компьютеры сейчас невероятно мощны.
Хуже того, в свободном доступе можно найти радужные таблицы, т.е. базы данных заранее просчитанных хэшей для миллионов паролей. Используя такие таблицы атакующий может проверять большое количество паролей в секунду.
С помощью добавления соли, некоторого случайного значения, к хэшу мы увеличиваем сложность процесса подбора паролей. Так как соль отличается от пароля к паролю, это делает невозможным использование радужных таблиц и отбрасывает атакующего обратно к обычному методу подбора пароля.
Несмотря на то, что подсоленные хэши не являются абсолютно безопасным методом хранения паролей, всё-таки это приемлемая середина между безопасностью и удобством.
Мы можем использовать эти низкоуровневые инструменты для создания представлений, которые позволяют пользователям регистрироваться на сайте. Почти каждый разработчик желает реализовать процедуру регистрации по своему, по этой причине Django оставляет эту часть работы для вас. К счастью, эта работа не сложна.
Для простейшего случая мы можем создать небольшое представление которое запрашивает необходимую информацию у пользователя и создаёт этого пользователя. Django предоставляет встроенную форму, которую вы можете использовать для этой задачи, как это сделано в нижеприведённом примере:
from django import oldforms as forms
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response
from django.contrib.auth.forms import UserCreationForm
def register(request):
form = UserCreationForm()
if request.method == 'POST':
data = request.POST.copy()
errors = form.get_validation_errors(data)
if not errors:
new_user = form.save(data)
return HttpResponseRedirect("/books/")
else:
data, errors = {}, {}
return render_to_response("registration/register.html", {
'form' : forms.FormWrapper(form, data, errors)
})
Эта форма использует шаблон
registration/register.html. Ниже
представлен вариант содержимого этого шаблона:
{% extends "base.html" %}
{% block title %}Регистрация пользователя{% endblock %}
{% block content %}
<h1>Регистрация пользователя</h1>
<form action="." method="post">
{% if form.error_dict %}
<p class="error">Пожалуйста исправьте нижеприведённые ошибки.</p>
{% endif %}
{% if form.username.errors %}
{{ form.username.html_error_list }}
{% endif %}
<label for="id_username">Логин:</label> {{ form.username }}
{% if form.password1.errors %}
{{ form.password1.html_error_list }}
{% endif %}
<label for="id_password1">Пароль:</label> {{ form.password1 }}
{% if form.password2.errors %}
{{ form.password2.html_error_list }}
{% endif %}
<label for="id_password2">Пароль (снова):</label> {{ form.password2 }}
<input type="submit" value="Зарегистрировать" />
</form>
{% endblock %}
Замечание
На время публикации этой книги класс django.contrib.auth.forms.UserCreationForm являлся устаревшим. Обратитесь к документации на http://www.djangoproject.com/documentation/0.96/forms/ для подробностей. Процесс миграции на новую версию форм, который описан в главе «Формы», будет завершён в ближайшем будущем.
После авторизации пользователя вся информация о нём и о его правах становится доступной в контексте шаблона при использовании RequestContext (см. главу «Расширения для шаблонной системы»).
Замечание
Эти переменные становятся доступными в контексте шаблона только в том случае, если вы используете RequestContext и ваш параметр TEMPLATE_CONTEXT_PROCESSORS содержит значение django.core.context_processors.auth (по умолчанию так и есть). И снова, см. подробности в главе «Расширения для шаблонной системы».
При использовании RequestContext текущий
пользователь (либо экземпляр User, либо
экземпляр AnonymousUser) хранится в
переменной шаблона {{ user }}:
{% if user.is_authenticated %}
<p>Добро пожаловать, {{ user.username }}. Спасибо, что не забываете нас.</p>
{% else %}
<p>Добро пожаловать, незнакомец. Кто вы?</p>
{% endif %}
Права текущего пользователя хранятся в переменной шаблона {{ perms }}. Технически это не просто переменная, а промежуточный слой между рядом методов проверки прав, описанных ранее.
Существует два способа использования объекта perm. Вы можете использовать, что-то подобное {{ perms.polls }} для проверки того, имеет ли пользователь вообще права для конкретного приложения. Также вы можете использовать что-то подобное {{ perms.polls.can_vote }} для проверки, имеет ли пользователь конкретное право.
Следовательно, вы можете проверять права в шаблоне с помощью оператора {% if %}:
{% if perms.polls %}
<p>У вас есть права на работу с приложением голосования.</p>
{% if perms.polls.can_vote %}
<p>Вы можете голосовать!</p>
{% endif %}
{% else %}
<p>У вас нет прав на использование приложения.</p>
{% endif %}
| Пред. | Уровень выше | След. |
| Сессии | Начало | Остальное: права, группы, сообщения и профайлы |
6 comments | Make a comment
Внимание!!!
Еcли у вас django версии 1.x.x и выше, пример в разделе "Управление регистрацией" работать не будет. см листинг примера в оригинальной книге http://djangobook.com/en/2.0/chapter14/
Вы правы. Но следует отметить, что данная глава так и не переведена, поэтому здесь временно размещён перевод из предыдущей версии книги.
С такими темпами как мы переводим она тут еще пол года будет висеть. Может проставишь пометки в не переведённых главах, для какой версии они актуальны.
В начале каждой непереведённой главы первым же абзацем написано.
Пусть засылают pull request с переводом ;)
в post форму нужно засунуть тег {% csrf_token %} чтобы чудо произошло.
в шаблоне login.html, разве будет отображаться надпись "Сожалеем, вы неправильно ввели логин или пароль", форма ведь нигде не передаётся в шаблон