
Django_-_podrobnoe_rukovodstvo
.pdf350 |
Глава 16. django.contrib |
<input type=”hidden” name=”confirm” value=”true”>
В этом случае простой POST-запрос на URL example.com/logout не повлечет за собой завершение сеанса работы с пользователем; для успешного выхода от имени пользователя не только должен быть отправлен запрос методом POST, но и параметр confirm в этом запросе должен иметь значение true.
Тем не менее, несмотря на принятые меры предосторожности, CSRFатака все равно возможна, просто вредоносной странице придется проделать чуть больше работы. Злоумышленник может создать форму, ведущую на ваш сайт, скрыть ее в невидимом теге <iframe>, а затем отправить форму автоматически с помощью JavaScript.
Предотвращение CSRF-атак
Как же все-таки защитить сайт от таких атак? Прежде всего, убедитесь, что никакие GET-запросы не производят побочные эффекты. Тогда, даже если вредоносный сайт включит какую-нибудь из ваших страниц в <iframe>, ничего страшного не случится.
Остаются POST-запросы. Второй эшелон обороны – включить в каждую форму <form>, отправляемую методом POST, скрытое секретное поле, значение которого генерируется в каждом сеансе заново. При обработке формы на сервере следует проверить это поле и возбудить исключение, если проверка не прошла. Именно так и работает система защиты от CSRF-атак в Django.
Использование дополнительного процессора CSRF
Пакет django.contrib.csrf состоит из единственного модуля: middleware.py. Он содержит класс CsrfMiddleware, реализующий защиту от CSRF-атак. Чтобы включить защиту, добавьте строку ‘django.contrib.csrf.middleware. CsrfMiddleware’ в параметр MIDDLEWARE_CLASSES. Этот процессор должен обрабатывать запрос после процессора SessionMiddleware, поэтому класс
CsrfMiddleware должен находиться в списке перед SessionMiddleware (так как дополнительные процессоры получают управление в порядке от последнего к первому). Кроме того, он должен обработать ответ до того, как тот будет сжат или еще каким-то образом преобразован, поэтому CsrfMiddleware должен находиться после GZipMiddleware. Добавлением
CsrfMiddleware в MIDDLEWARE_CLASSES ваша задача исчерпывается. Дополнительные сведения см. в разделе «Порядок строк в MIDDLEWARE_ CLASSES» в главе 15.
Для интересующихся поясним, как действует процессор CsrfMiddleware.
•• Он модифицирует исходящие ответы, добавляя во все POST-формы скрытое поле csrfmiddlewaretoken, значением которого является свертка идентификатора сеанса плюс секретный ключ. Если идентификатор сеанса не определен, процессор не модифицирует ответ,

Защита от атак CSRF |
351 |
поэтому, если сеансы не используются, накладные расходы пренебрежимо малы.
•• Для всех входящих POST-запросов, в которых присутствует сеансовый cookie, процессор проверяет наличие и правильность параметра csrfmiddlewaretoken. Если это не так, пользователь получит ошибку 403 с сообщением «Cross Site Request Forgery detected. Request aborted». (Обнаружена подделка HTTP-запроса. Запрос отклонен.)
Тем самым гарантируется, что методом POST могут быть отправлены только формы с вашего сайта.
Процессор преднамеренно нацелен только на запросы, отправляемые методом POST (и соответствующие POST-формы). Как отмечалось выше, GET-запросы не должны иметь побочных эффектов, и ответственность за их обработку полностью возлагается на вас.
POST-запросы, не содержащие сеансового cookie, никак не защищены, но они и не должны нуждаться в защите, поскольку вредоносный сайт может отправлять такого типа запросы в любом случае.
Чтобы исключить модификацию запросов в формате, отличном от HTML, процессор предварительно проверяет заголовок Content-Type. Изменения вносятся только в страницы типа text/html или application/ xml+xhtml.
Ограничения дополнительного процессора CSRF
Для правильной работы процессора CsrfMiddleware необходима подсис тема сеансов Django (см. главу 14). Если вы пользуетесь нестандартными механизмами управления сеансами и аутентификацией, то этот процессор вам не поможет.
Если приложение создает HTML-страницы и формы каким-то необычным способом (например, посылает фрагменты HTML с помощью инструкций document.write в сценарии на языке JavaScript), то можно обойти фильтр, который добавляет в форму скрытое поле. В таком случае данные в отправленной форме будут отвергнуты. (Это происходит потому,чтоCsrfMiddleware применяетдлядобавленияполя csrfmiddlewaretoken
регулярное выражение еще до отправки HTML-страницы клиенту, а некоторые, не вполне корректные фрагменты HTML, это выражение не распознает.) Если вы грешите на такое развитие событий, посмотрите исходный код страницы в броузере и проверьте, присутствует ли в форме поле csrfmiddlewaretoken.
Дополнительные сведения о CSRF-атаках и пример см. на странице http://en.wikipedia.org/wiki/CSRF1.
1Аналогичная страница с информацией на русском языке находится по адресу http://ru.wikipedia.org/wiki/CSRF. – Прим. науч. ред.

352 |
Глава 16. django.contrib |
Удобочитаемость данных
Пакет django.contrib.humanize содержит ряд шаблонных фильтров, которые могут использоваться для придания данным «человеческого облика». Чтобы включить эти фильтры в свое приложение, добавьте строку
‘django.contrib.humanize’ в параметр INSTALLED_APPS. Затем просто включите в шаблон директиву {% load humanize %}. Ниже описаны входящие в пакет фильтры1.
apnumber
Фильтр возвращает словесный эквивалент цифр от 1 до 9, то есть количественное числительное, например:
•• «1» преобразуется в «one»
•• «2» преобразуется в «two»
•• «10» остается без изменения («10»)
Передать можно как целое число, так и строковое представление целого.
intcomma
Фильтр преобразует целое число в строку, где группы по три цифры разделены запятыми, например:
•• «4500» преобразуется в «4,500»
•• «45000» преобразуется в «45,000»
•• «450000» преобразуется в «450,000»
•• «4500000» преобразуется в «4,500,000» Передать можно как целое число, так и строковое представление целого.
intword
Фильтр преобразует большое целое число в эквивалентное текстовое представление. Лучше всего применять его к числам, значение которых больше миллиона. Поддерживаются величины до 1 квадриллиона (1,000,000,000,000,000). Например:
•• «1000000» преобразуется в «1.0 million»
•• «1200000» преобразуется в «1.2 million»
•• «1200000000» преобразуется в «1.2 billion»
Передать можно как целое число, так и строковое представление целого.
1Текстовое представление чисел в этих фильтрах жестко определено в программном коде, поэтому нет простой возможности реализовать вывод числительных на русском языке иначе, чем написать собственные фильтры. –
Прим. науч. ред.

Фильтры разметки |
353 |
ordinal
Фильтр преобразует целое число в порядковое числительное, например:
•• «1» преобразуется в «1st»
•• «2» преобразуется в «2nd»
•• «3» преобразуется в «3rd»
•• «254» преобразуется в «254th»
Передать можно как целое число, так и строковое представление целого.
Фильтры разметки
Пакет django.contrib.markup содержит ряд шаблонных фильтров, реализующих некоторые распространенные языки разметки:
•• textile: реализует язык разметки Textile (http://en.wikipedia.org/wiki/ Textile_%28markup_language%291).
•• markdown: реализует язык разметки Markdown (http://en.wikipedia. org/wiki/Markdown2).
•• restructuredtext: реализует язык разметки reStructured Text (http:// en.wikipedia.org/wiki/ReStructuredText).
Во всех случаях фильтр ожидает получить на входе строку с разметкой и возвращает ее представление в формате HTML. Например, фильтр textile преобразует текст в формате Textile в формат HTML:
{% load markup %}
{{ object.content|textile }}
Чтобы активировать эти фильтры, добавьте строку ‘django.contrib.markup’ в параметр INSTALLED_APPS. После этого достаточно включить в шаблон директиву {% load markup %}. Более подробное описание приводится в исходном коде (файл django/contrib/markup/templatetags/markup.py).
Что дальше?
Многие дополнительные подсистемы (CSRF, подсистема аутентификации и т. д.) реализованы в виде дополнительных процессоров. Так называется код, работающий до или после стандартной обработки запроса, который может произвольным образом модифицировать запросы и ответ, расширяя тем самым фреймворк. В следующей главе мы рассмотрим встроенные в Django дополнительные процессоры и объясним, как написать свой собственный.
1Страница в Википедии на русском языке находится по адресу http:// ru.wikipedia.org/wiki/Textile_%28язык_разметки%29. – Прим. науч. ред.
2Страница в Википедии на русском языке находится по адресу http:// ru.wikipedia.org/wiki/Markdown. – Прим. науч. ред.
17
Дополнительные процессоры
Иногда возникает необходимость реализовать дополнительную обработку всех без исключения запросов, обслуживаемых Django. Такая обработка может понадобиться, чтобы модифицировать запрос перед передачей его в функцию представления, записать в журнал какиенибудь сведения о запросе для отладки и т. д.
Это можно сделать с помощью механизма дополнительных процессоров, которые подключаются к процедуре обработки запроса и ответа и позволяют глобально изменять входные и выходные данные.
Каждый дополнительный процессор отвечает за реализацию одной конкретной функции. Если вы читали книгу подряд, то уже неоднократно встречались с дополнительными процессорами.
•• Все средства управления сеансами и пользователями, рассмотренные в главе 14, в своей работе опираются на дополнительные процессоры (точнее, процессоры открывают представлениям доступ к объектам request.session и request.user).
•• Механизм кэширования на уровне сайта, рассмотренный в главе 15, – это не что иное, как дополнительный процессор, который обходит вызов функции представления, если ответ уже находится в кэше.
•• Плоские страницы, объекты переадресации и реализация механизма защиты от CSRF-атак (глава 16) также реализованы с помощью дополнительных процессоров.
В этой главе мы детально рассмотрим принципы работы дополнительных процессоров и покажем, как можно написать такой процессор самостоятельно.

Что такое дополнительный процессор? |
355 |
Что такое дополнительный процессор?
Начнем с очень простого примера.
На высоконагруженных сайтах фреймворк Django часто развертывают за балансировщиком нагрузки (см. главу 12). Это может вызвать некоторые затруднения, одно из которых заключается в том, что теперь в роли IP-адреса клиента (request.META[“REMOTE_IP”]) выступает адрес балансировщика, а не действительного клиента, отправившего запрос. Балансировщики нагрузки решают эту проблему, добавляя в исходный запрос специальный HTTP-заголовок X-Forwarded-For, где указывается IP-адрес истинного клиента.
Приведем пример простого дополнительного процессора, который позволяет сайтам, находящимся за прокси-сервером, выполняющим балансировку, находить истинный IP-адрес там, где ему положено быть, – в заголовке META[“REMOTE_ADDR”]:
class SetRemoteAddrFromForwardedFor(object): def process_request(self, request):
try:
real_ip = request.META[‘HTTP_X_FORWARDED_FOR’] except KeyError:
pass else:
#HTTP_X_FORWARDED_FOR может быть списком IP-адресов,
#разделенных запятой. Берем первый из них.
real_ip = real_ip.split(“,”)[0] request.META[‘REMOTE_ADDR’] = real_ip
Примечание
Хотя HTTP-заголовок на самом деле называется X-Forwarded-For, Django предоставляет к нему доступ по имени request.META[‘HTTP_X_FORWARDED_FOR’]. Все заголовки в запросе, за исключением content-length и content-type, преобразуются в ключи словаря request.META путем преобразования символов в верхний регистр, замены дефисов символами подчеркивания и добавления префикса HTTP_.
Если установить этот дополнительный процессор (см. следующий раздел), то значение заголовка X-Forwarded-For в любом запросе автоматически будет записываться в элемент словаря request.META[‘REMOTE_ADDR’]. В результате приложению Django будет безразлично, стоит перед ним балансирующий прокси-сервер или нет; оно просто будет обращаться к элементу request.META[‘REMOTE_ADDR’] и действовать так, как если бы никакого прокси-сервера не существовало.
На самом деле такая потребность возникает настолько часто, что этот дополнительный процессор уже встроен в Django. Он находится в пакете django.middleware.http, и мы еще вернемся к нему ниже.
356 |
Глава 17. Дополнительные процессоры |
Установка дополнительных процессоров
Если вы читали книгу подряд, то уже встречались с многочисленными примерами установки дополнительных процессоров; они были необходимы для многих приложений, описанных в предыдущих главах. Тем не менее для полноты картины расскажем, как производится установка.
Чтобы активировать дополнительный процессор, добавьте его в кортеж MIDDLEWARE_CLASSES в файле параметров. В этом кортеже каждый процессор представлен строкой, содержащей полный путь Python к имени соответствующего класса. Вот, например, как выглядит параметр MIDDLEWARE_CLASSES в проекте, который по умолчанию создается командой django-admin.py startproject:
MIDDLEWARE_CLASSES = ( ‘django.middleware.common.CommonMiddleware’, ‘django.contrib.sessions.middleware.SessionMiddleware’, ‘django.contrib.auth.middleware.AuthenticationMiddleware’,
)
Для работы самого фреймворка Django никакие дополнительные процессоры не нужны, то есть кортеж MIDDLEWARE_CLASSES может быть пустым. Однако мы рекомендуем активировать хотя бы процессор CommonMiddleware, который опишем чуть ниже.
Порядок следования процессоров имеет важное значение. На этапах получения запроса и работы представления Django вызывает дополнительные процессоры в том порядке, в котором они перечислены в MIDDLEWARE_CLASSES, а на этапах формирования ответа и обработки исключений – в обратном порядке. Таким образом, дополнительные процессоры – это своего рода «обертка» вокруг функции представления: при обработке запроса список процессоров просматривается сверху вниз, а при формировании ответа – снизу вверх.
Методы дополнительных процессоров
Разобравшись с принципами работы дополнительных процессоров и порядком их установки, посмотрим, какие методы можно определять в реализующих их классах.
Инициализация: __init__(self)
Метод __init__() применяется для инициализации класса дополнительного процессора.
Из соображений производительности каждый активированный класс процессора инициализируется только один раз на протяжении времени жизни серверного процесса. Это означает, что метод __init__() вызывается однократно – на этапе инициализации сервера, – а не для каждого запроса.
Методы дополнительных процессоров |
357 |
Обычно метод __init__() реализуют, чтобы проверить, а нужен ли вообще данный процессор. Если __init__() возбудит исключение django. core.exceptions.MiddlewareNotUsed, то Django удалит процессор из списка вызываемых. Эту возможность можно использовать, чтобы проверить, установлено ли программное обеспечение, необходимое дополнительному процессору, или узнать, работает ли сервер в режиме отладки, или выполнить аналогичные функции, смотря по обстоятельствам.
Если в классе процессора определен метод __init__(), то он не должен принимать никаких аргументов, кроме self.
Препроцессор запроса: process_request(self, request)
Этот метод вызывается сразу после получения запроса – еще до того, как Django проанализирует URL и определит, какое представление следует вызвать. Ему передается объект HttpRequest, который он может модифицировать произвольным образом.
Метод process_request() должен вернуть либо значение None, либо объект HttpResponse.
•• При получении значения None Django продолжит обработку запроса, вызывая по-порядку все остальные дополнительные процессоры, а затем требуемое представление.
•• При получении объекта HttpResponse Django не станет вызывать больше никаких дополнительных процессоров или представление, а сразу вернет этот объект клиенту.
Препроцессор представления: process_view(self, request, view, args, kwargs)
Этот метод вызывается после препроцессора запроса и после того, как Django определит, какое представление следует вызвать, но до вызова этого представления.
Ему передаются аргументы, перечисленные в табл. 17.1.
Таблица 17.1. Аргументы, передаваемые process_view()
Аргумент |
Пояснение |
|
|
request |
Объект HttpRequest. |
view |
Функция Python, которую Django вызовет для обработки |
|
запроса. Это сам объект функции, а не ее имя в виде строки. |
args |
Список позиционных аргументов, передаваемых |
|
представлению, не включая аргумент request |
|
(который всегда передается представлению первым). |
kwargs |
Словарь именованных аргументов, передаваемый |
|
представлению. |
|
|

358 |
Глава 17. Дополнительные процессоры |
Как и process_request(), метод process_view() должен вернуть значение
None или объект HttpResponse.
•• При получении значения None Django продолжит обработку запроса, вызывая по порядку все остальные дополнительные процессоры, а затем требуемое представление.
•• При получении объекта HttpResponse Django не станет вызывать больше никаких дополнительных процессоров или представление, а сразу вернет этот объект клиенту.
Постпроцессор ответа: process_response(self, request, response)
Этот метод вызывается после того, как функция представления отработала и сконструировала ответ. Процессор может модифицировать содержимое ответа. Очевидный пример – сжатие HTML-содержимого применением алгоритма gzip.
Параметры метода не нуждаются в пространных пояснениях: request – объект запроса, response – объект ответа, возвращаемый представлением.
В отличие от препроцессоров запроса и представления, которые могут возвращать значение None, метод process_response() обязан вернуть объект HttpResponse. Это может быть тот самый объект, который был ему передан (возможно, модифицированный), или совершенно новый.
Постпроцессор обработки исключений: process_exception(self, request, exception)
Этот метод вызывается, только если представление возбудит исключение и оно не будет обработано. Его можно использовать для отправки уведомлений об ошибках, записи аварийного дампа в журнал и даже для автоматического восстановления после ошибки.
В качестве параметров функции передаются все тот же объект request
иобъект exception – то самое исключение, которое возбудила функция представления.
Метод process_exception() должен вернуть значение None или объект
HttpResponse.
•• При получении значения None Django продолжит обработку запроса, применяя встроенный механизм обработки исключений.
•• При получении объекта HttpResponse Django вернет именно его в обход встроенного механизма обработки исключений.
Примечание
В комплект поставки Django входит ряд дополнительных процессоров (они рассматриваются в следующем разделе), которые могут служить неплохими примерами. Ознакомившись с их реализацией, вы получите представление

Встроенные дополнительные процессоры |
359 |
о колоссальных возможностях этого механизма. На вики-странице Django по адресу http://code.djangoproject.com/wiki/ContributedMiddleware приводится немало примеров, предложенных сообществом.
Встроенные дополнительные процессоры
В состав Django входят дополнительные процессоры для решения типичных задач. Они обсуждаются в следующих разделах.
Процессоры для поддержки аутентификации
Класспроцессора:django.contrib.auth.middleware.AuthenticationMiddleware.
Этот процессор обеспечивает поддержку аутентификации. В каждый объект HttpRequest, соответствующий входящему запросу, он добавляет атрибут request.user, который представляет текущего аутентифицированного пользователя.
Подробную информацию см. в главе 14.
Процессор типичных операций
Класс процессора: django.middleware.common.CommonMiddleware. Добавляет некоторые удобства для перфекционистов.
•• Запрещает доступ типам броузеров, перечисленным в параметре
DISALLOWED_USER_AGENTS. Если этот параметр определен, его значением должен быть список откомпилированных регулярных выражений, сопоставляемых с заголовком user-agent для каждого входящего запроса. Ниже приведен фрагмент файла параметров:
import re
DISALLOWED_USER_AGENTS = ( re.compile(r’^OmniExplorer_Bot’), re.compile(r’^Googlebot’)
)
Обратите внимание на инструкцию import re. Она присутствует, потому что все значения в списке DISALLOWED_USER_AGENTS должны быть откомпилированными регулярными выражениями (то есть результатами работы метода re.compile()). Файл параметров – это обычный программный код на языке Python, поэтому нет ничего необычного в том, чтобы включить в него инструции import.
•• Выполняет перезапись URL в соответствии со значениями параметров APPEND_SLASH и PREPEND_WWW. Если параметр APPEND_SLASH содержит значение True, то URL, в котором отсутствует завершающий слеш, будет переадресован на тот же URL, но с завершающим символом слеша, если только последний компонент пути не содержит точ-