Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Django_-_podrobnoe_rukovodstvo

.pdf
Скачиваний:
373
Добавлен:
01.03.2016
Размер:
5 Мб
Скачать
☆

310

Глава 14. Сеансы, пользователи и регистрация

В случае успешной аутентификации пользователь по умолчанию будет переадресован на страницу /accounts/profile/. Чтобы изменить URLадрес, укажите его в атрибуте value скрытого поля next в форме. Это значение можно также передать в параметре GET-запроса к представлению login, тогда оно будет автоматически добавлено в контекст в виде переменной next, которую можно поместить в скрытое поле.

Представление logout работает несколько иначе. По умолчанию оно использует шаблон­ registration/logged_out.html (обычно он содержит сообщение «Вы успешно вышли из системы»)­ . Однако его можно вызывать с дополнительным аргументом next_page, в котором указывается URL страницы, куда следует перейти.

Разрешение доступа только аутентифицированным пользователям

Но для чего все это нужно? Конечно, для того чтобы ограничить доступ к некоторым частям сайта.

Простой и прямолинейный способ закрыть доступ к странице состоит в том, чтобы проверить результат, возвращаемый методом request.user. is_authenticated(), и переадресовать пользователя на страницу входа:

from django.http import HttpResponseRedirect

def my_view(request):

if not request.user.is_authenticated():

return HttpResponseRedirect(‘/accounts/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):

# ...

Декоратор login_required выполняет следующие действия:

•• Если пользователь не аутентифицирован, производится переадресация на URL /accounts/login/, а текущий URL передается в параметре next строки запроса, например: /accounts/login/?next=/polls/3/;

Пользователи и аутентификация

311

•• Если пользователь аутентифицирован, выполнение функции представления продолжается как обычно. Внутри функции представления можно считать, что пользователь аутентифицирован.

Ограничение доступа по результатам проверки

Ограничение доступа в зависимости от наличия определенного разрешения или на основе результатов еще какой-нибудь проверки, с возможной переадресацией на страницу входа, реализуется точно так же.

Самый простой способ состоит в том, чтобы выполнить проверку объекта 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 автоматически не проверяет, был ли пользователь User аутентифицирован; это вы должны сделать самостоятельно.

В примере выше показан также второй (необязательный аргумент) login_url, который позволяет указать URL страницы входа (по умолчанию /accounts/login/). Если пользователь не прошел проверку, декоратор user_passes_test переадресует его на страницу, определяемую аргументом login_url.

Поскольку довольно часто приходится проверять, обладает ли пользователь некоторым разрешением, Django предоставляет для этого слу-

312

Глава 14. Сеансы, пользователи и регистрация

чая вспомогательную функцию: декоратор 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 можно использовать любой ограничивающий декоратор.

Управление пользователями, разрешениями и группами

Самый простой способ управления системой­ аутентификации дает административный интерфейс. В главе 6 рассказывается, как с его помощью редактировать учетные записи пользователей и управлять их разрешениями. В большинстве случаев так вы и будете поступать.

Но существует и низкоуровневый 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() самостоятельно не вызывает метод

Пользователи и аутентификация

313

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 напрямую. Пароль хранится в виде свертки, поэтому его нельзя редактировать непосредственно.

Если говорить точнее, атрибут password объекта User – это строка в формате

hashtype$salt$hash

Она состоит из трех частей: типа свертки (hashtype), затравки (salt) и самой свертки (hash), разделенных знаком доллара. Часть hashtype может принимать значение, вычисленное по алгоритму sha1 (по умолчанию) или md5; это алгоритм несимметричного хеширования пароля. Часть salt – случайная строка, используемая для внесения элемента случайности в свертку пароля. Например:

sha1$a1976$a36cc8cbf81742a8fb52e221aaeab48ed7f58ab4

Для вычисления и проверки этих значений реализация вызывает методы User.set_password() и User.check_password().

Свертки с затравкой1

Сверткой (hash) называется несимметричная криптографическая функция. Ее легко вычислить, но практически невозможно обратить, то есть получить из свертки исходное значение.

Если бы пароли хранились в открытом виде, то всякий, получивший доступ к базе паролей, смог бы немедленно узнать пароли всех пользователей. Но коль скоро хранятся не сами пароли, а их свертки, то последствия компрометации базы данных не настолько опасны.

1 Термин «salted hash» в литературе по криптографии переводится и как «свертка с затравкой», и как «хеш привязка». – Прим. ред.

314

Глава 14. Сеансы, пользователи и регистрация

Однако злоумышленник, завладевший базой паролей, все же может применить атаку полным перебором, то есть вычислить свертку миллионов паролей и сравнивать ее с тем, что хранится в базе. На это потребуется время, но не так много, как вам кажется.

Хуже того, существуют так называемые радужные таблицы (rain­ bow tables), то есть базы данных, содержащие свертки миллионов паролей, вычисленные заранее. При наличии радужной таблицы опытный злоумышленник может взломать большинство паролей за несколько секунд.

Добавление затравки – по существу, случайного начального значения – к сохраненной свертке увеличивает сложность взлома паролей. Поскольку в каждом пароле используется своя затравка, то применение радужных таблиц становится бессмысленным, и злоумышленник вынужден будет обратиться к атаке полным перебором, которая сама по себе усложняется из-за наличия дополнительной энтропии, вносимой затравкой.

Хотя свертки с затравкой нельзя назвать абсолютно защищенным способом хранения паролей, все же это приемлемый компромисс между безопасностью и удобством.

Управление регистрацией

Вышеперечисленные низкоуровневые средства можно использовать для реализации представлений, позволяющих создавать новые учетные записи. Разные разработчики реализуют регистрацию по-разному, поэтому Django оставляет написание представления на ваше усмотрение. Впрочем, это несложно.

В простейшем варианте представление может запрашивать обязательную информацию о пользователе и создавать учетную запись. Для этой цели в Django имеется встроенная форма:

from django import forms

from django.contrib.auth.forms import UserCreationForm from django.http import HttpResponseRedirect

from django.shortcuts import render_to_response

def register(request):

if request.method == ‘POST’:

form = UserCreationForm(request.POST) if form.is_valid():

new_user = form.save()

return HttpResponseRedirect(“/books/”)

else:

form = UserCreationForm()

return render_to_response(“registration/register.html”, {

Пользователи и аутентификация

315

‘form’: form,

})

В этой форме предполагается наличие шаблона­ с именем registration/ register.html. Выглядеть он может так:

{% extends “base.html” %}

{% block title %}Создать учетную запись{% endblock %}

{% block content %}

<h1>Создать учетную запись</h1>

<form action=”” method=”post”> {{ form.as_p }}

<input type=”submit” value=”Создать учетную запись”> </form>

{% endblock %}

Использование данных аутентификации в шаблонах­

Объект текущего аутентифицированного пользователя и его разрешения попадают в контекст шаблона,­ если вы пользуетесь классом

RequestContext (см. главу 9).

Примечание

Строго говоря, эти переменные попадают в контекст шаблона,­ только если используется класс RequestContext и параметр TEMPLATE_CONTEXT_PROCESSORS содержит строку “django.core.context_processors.auth” (по умолчанию так и есть). Подробнее см. главу 9.

При использовании объекта RequestContext информация о текущем пользователе (экземпляр класса User или AnonymousUser) сохраняется в шаб­ лонной переменной {{ user }}:

{% if user.is_authenticated %}

<p>Добро пожаловать, {{ user.username }}. Спасибо, что зашли.</p> {% else %}

<p>Добро пожаловать, незнакомец. Пожалуйста, назовите себя.</p> {% endif %}

Разрешения пользователя хранятся в шаблонной­ переменной {{ perms }}. Это адаптированный к шаблонам­ прокси-объект для доступа к нескольким методам проверки разрешений, которые будут описаны ниже.

Объект perms может использоваться двумя способами. Можно написать инструкцию вида {% if perms.polls %}, в которой проверить наличие у пользователя хоть какого-нибудь разрешения для работы с данным приложением, или инструкцию вида {% if perms.polls.can_vote %}, где проверить наличие конкретного разрешения.

316

Глава 14. Сеансы, пользователи и регистрация

Таким образом, разрешения можно проверять внутри шаблона,­ в инструкциях {% if %}:

{% if perms.polls %}

<p>Вам разрешено что-то делать в опросах.</p> {% if perms.polls.can_vote %}

<p>Вы можете голосовать!</p> {% endif %}

{% else %}

<p>Вам ничего не разрешено делать в опросах.</p> {% endif %}

Разрешения, группы и сообщения

В подсистеме­ аутентификации есть еще несколько частей, которые раньше мы упоминали лишь мимоходом. Теперь же рассмотрим их подробнее.

Разрешения

Разрешения – это простой способ указать, что пользователь или группа имеют право на выполнение некоторого действия. Обычно работа с разрешениями происходит в административном интерфейсе Django, но ничто не мешает использовать их и в собственном коде.

В административном интерфейсе Django разрешения используются следующим образом:

•• Доступ к форме «Добавить» и к операции добавления объекта открыт только пользователям, имеющим разрешение на добавление объектов данного типа.

•• Доступ к списку для изменения, к форме «Изменить» и к операции изменения объекта открыт только пользователям, имеющим разрешение на изменение объектов данного типа.

•• Доступ к операции удаления объекта открыт только пользователям, имеющим разрешение на удаление объектов данного типа.

Разрешения определяются глобально для типов объектов, а не для отдельных экземпляров. Например, можно сказать «Маша может изменять новости», но нельзя сказать «Маша может изменять только те новости, которые создала сама» или «Маша может изменять только новости с определенным состоянием, датой публикации или идентификатором».

Три основных разрешения – добавление, изменение, удаление – автоматически создаются для каждой модели Django. Технически они добавляются в таблицу базы данных auth_permission при выполнении команды manage.py syncdb.

Разрешения, группы и сообщения

317

Эти разрешения имеют вид “<app>.<action>_<object_name>”. Иными словами, если имеется приложение polls с моделью Choice, то будут созданы разрешения “polls.add_choice”, “polls.change_choice” и “polls.delete_ choice”.

Как и учетные записи, разрешения реализованы в виде модели Django, находящейся в модуле django.contrib.auth.models. Следовательно, для взаимодействия с разрешениями напрямую можно использовать API доступа к базе данных.

Группы

Группы – это универсальный механизм классификации пользователей, позволяющий назначить всем пользователям, входящим в группу, общее название и общий набор разрешений. Пользователь может входить в любое число групп.

Любой член группы автоматически получает все разрешения, выданные данной группе. Например, если группе Редакторы сайта выдано разрешение can_edit_home_page, то любой пользователь в этой группе тоже получит это разрешение.

Группы также предоставляют удобный способ объединить нескольких пользователей под общим названием и реализовать для них некую расширенную функциональность. Например, можно создать группу Особые пользователи и написать код, который откроет входящим в нее пользователям доступ к разделу сайта, предназначенному только для членов этой группы, или разошлет им по электронной почте сообщение, адресованное членам группы.

Группами, как и учетными записями, проще всего управлять в административном интерфейсе. Но поскольку группы – это всего лишь модели, находящиеся в модуле django.contrib.auth.models, то для работы с ними на низком уровне можно применить API доступа к базе данных.

Сообщения

Система сообщений – это простой способ организации очереди сообщений для определенных пользователей. Каждое сообщение ассоциируется с объектом User. Таких понятий, как срок хранения или временная метка, в очередях не существует.

Вадминистративном интерфейсе Django сообщения выводятся после успешного выполнения операций. Например, после создания объекта

вверхней части страницы появится сообщение «Объект успешно создан» (The object was created successfully).

Всвоих приложениях вы можете использовать тот же самый API для постановки в очередь и отображения сообщений. Он достаточно прост:

318

Глава 14. Сеансы, пользователи и регистрация

•• Чтобы создать новое сообщение, следует вызвать метод user.message_ set.create(message=’message_text’).

•• Чтобы извлечь и удалить сообщения, следует вызвать метод user.get_ and_delete_messages(), который возвращает список объектов Message

вочереди пользователя (если она не пуста) и удаляет их из очереди.

Вследующем примере представления система­ сохраняет сообщение для пользователя после создания списка воспроизведения:

def create_playlist(request, songs):

#Создать список воспроизведения, содержащий несколько песен.

#...

request.user.message_set.create( message=”Список воспроизведения создан.”

)

return render_to_response(“playlists/create.html”, context_instance=RequestContext(request))

Если используется объект RequestContext, то сообщения текущего аутентифицированного пользователя становятся доступны в шаблоне­ в виде шаблонной­ переменной {{ messages }}. Вот пример шаблона­ для отображения сообщений:

{% if messages %} <ul>

{% for message in messages %} <li>{{ message }}</li>

{% endfor %} </ul>

{% endif %}

Отметим, что внутренняя реализация объекта RequestContext вызывает метод get_and_delete_messages, поэтому все сообщения удаляются из очереди, даже если вы не стали их отображать.

Наконец, добавим, что подсистема­ сообщений работает только для пользователей, чьи учетные записи хранятся в базе данных. Чтобы отправить сообщение анонимному пользователю, придется работать с подсис­ темой сеансов напрямую.

Что дальше?

Подсистемы­ сеансов и аутентификации – вещь нетривиальная. Как правило, вам не понадобятся все функции, описанные в этой главе, но, когда возникает необходимость в сложных взаимодействиях с пользователями, хорошо иметь всю эту мощь под рукой.

Вследующей главе мы рассмотрим инфраструктуру кэширования

вDjango, позволяющую повысить производительность приложения.

15

Кэширование

Что является фундаментальным свойством динамических веб-сайтов? Правильно, динамичность. При каждом обращении к странице сервер производит самые разные вычисления – запросы к базе данных, отображение шаблонов­ и другие операции, – чтобы создать визуальный образ, видимый клиенту. С точки зрения накладных расходов это получается гораздо дороже, чем обычное чтение файла с диска.

Для большинства веб-приложений такие издержки не критичны. Ведь большая часть веб-приложений – не гиганты типа washingtonpost.com или slashdot.org, а сайты небольшого или среднего размера со скромным трафиком. Но для крупных сайтов сведение накладных расходов к минимуму – первоочередная задача.

Тут-то и приходит на помощь кэширование.

Кэширование – это сохранение результатов трудоемких вычислений, позволяющее не повторять эти же вычисления в следующий раз. Представленный ниже псевдокод объясняет, как этот подход работает для динамически генерируемой веб-страницы:

пытаемся найти в кэше страницу, соответствующую заданному URL если страница присутствует в кэше:

вернуть кэшированную страницу иначе:

сгенерировать новую страницу сохранить сгенерированную страницу в кэше (для следующего раза) вернуть сгенерированную страницу

В состав Django входит надежная система­ кэширования, позволяющая сохранять динамические страницы, чтобы их не приходилось заново вычислять при каждом запросе. Для удобства Django предлагает различные уровни кэширования: можно кэшировать результат работы отдельных представлений, только фрагменты, генерация которых обходится дорого, или весь сайт.