Python/Django — HTTP-аутентификация

Я хочу поместить базовую HTTP-аутентификацию где-нибудь в свой код, если выполняется определенное условие. Условие является параметром запроса. Чтобы упростить, скажем, я хочу, чтобы это было в представлении Django.

Я знаю, как это сделать на PHP. Это кусок кода, который делает именно то, что я хочу:

if (isset($_REQUEST['key']) && $_REQUEST['key'] === 'value') {
    if (!isset($_SERVER['PHP_AUTH_USER'])) {
        header('WWW-Authenticate: Basic realm="My Realm"');
        header('HTTP/1.0 401 Unauthorized');
        echo 'Text to send if user hits Cancel button';
        exit;
    } else {
        echo "<p>Hello {$_SERVER['PHP_AUTH_USER']}.</p>";
        echo "<p>You entered {$_SERVER['PHP_AUTH_PW']} as your password.</p>";
    }
}

Я могу поместить этот кусок кода в любое место моего PHP-скрипта, и когда выполнение достигнет этих строк, будет запрошена аутентификация. Если аутентификация не удалась, выполнение скрипта остановится, и вместе с указанным сообщением будет отправлен заголовок 401 Unauthorized. Это то, что я хочу в Python/Django. Вот кусок кода, помогите мне его заполнить:

def some_view(request):
    if request.REQUEST.get('key') == 'value':
        # FILL ME IN

Что я хочу:

  • чтобы иметь возможность поместить этот фрагмент в код в любом месте кода и потребовать аутентификации от пользователя (предположим, что переменная запроса доступна мне, экземпляр django.http.response.HttpResponse также в качестве ответа)
  • чтобы иметь возможность указать условие, при котором будет запрашиваться аутентификация (например, наличие параметра в запросе)
  • остановить дальнейшее выполнение, если пользователь предоставит неверные учетные данные, и вернуть ответ 401 Unauthorized
  • чтобы решение было переносимым (скопируйте/вставьте фрагмент кода в другое место, и аутентификация будет запущена там, как только до него дойдет выполнение)

Чего я НЕ хочу:

  • иметь жестко закодированное требование аутентификации при вызове функции (с помощью декоратора)
  • изменить любые глобальные настройки моего веб-приложения
  • чтобы изменить что-либо еще за пределами части кода, я хочу, чтобы моя аутентификация запускалась
  • чтобы для этого были установлены какие-либо дополнительные пакеты
  • совместно использовать учетные данные этой аутентификации со стандартной аутентификацией для доступа к CMS, уже настроенной в приложении

person morgoth84    schedule 29.05.2014    source источник


Ответы (2)


from django.http import HttpResponse

def some_view(request):
    if request.REQUEST.get('key') == 'value':
        if not request.user.is_authenticated():
            response = HttpResponse('Text to send if user hits Cancel button', status=401)
            response['WWW-Authenticate'] = 'Basic realm="My Realm'
            return response
        ...

Это вернет код состояния 401, но потребует, чтобы вы предоставили свой собственный контент/шаблон. Если вы можете вернуть код состояния 403, вы можете использовать встроенное исключение PermissionDenied, которое возвращает страницу 403 на основе вашего шаблона 403.html:

from django.core.exceptions import PermissionDenied

def some_view(request):
    if request.REQUEST.get('key') == 'value':
        if not request.user.is_authenticated():
            raise PermissionDenied
        ...

ИЗМЕНИТЬ:

Чтобы реализовать базовую HTTP-аутентификацию, вам нужно настроить свой веб-сервер (Apache, nginx, что бы вы ни использовали) для обработки этого. Вы должны проверить документацию вашего веб-сервера о том, как это сделать.

Django реализует RemoteUserBackend, чтобы позволить пользователям входить в систему с использованием базовой аутентификации, но при этом используется та же модель пользователя, что и в обычной ModelBackend, поэтому они не являются отдельными. К счастью, промежуточное ПО и бэкенд довольно прост. Веб-сервер отклонит любой запрос с недействительными учетными данными, поэтому каждый раз, когда устанавливается заголовок 'REMOTE_USER' (или любое другое значение заголовка, которое вы используете), пользователь правильно аутентифицируется. Этот заголовок будет доступен в request.META, поэтому, чтобы проверить, аутентифицирован ли пользователь с помощью базовой аутентификации, вы можете использовать этот код:

from django.http import HttpResponse

def some_view(request):
    if request.REQUEST.get('key') == 'value':
        if request.META.get('REMOTE_USER', None):
            do_something()
        else:
            response = HttpResponse('Text to send if user hits Cancel button', status=401)
            response['WWW-Authenticate'] = 'Basic realm="My Realm'
            return response
        ...
person knbk    schedule 29.05.2014
comment
Это кажется правильным, но в моем случае есть одна проблема: request.user — это пользователь, который зарегистрирован в CMS Django, и я этого не хочу. Я бы хотел, чтобы эта аутентификация была полностью отделена от той, что используется в Django. Если вы вошли в один, это не имеет ничего общего с другим. В основном я хочу увидеть предоставленное имя пользователя и пароль и проверить их самостоятельно. Это возможно как-то? - person morgoth84; 29.05.2014
comment
@ morgoth84 Обновил мой ответ. - person knbk; 29.05.2014
comment
Спасибо, используя ваш ввод (и эту ссылку для получения аутентификации UN/PW: djangosnippets.org/snippets/1304), мне удалось сделать все, что я хотел, иметь возможность запрашивать аутентификацию в любом месте кода и отделить ее от других методов аутентификации, которые уже используются в существующем приложении. Когда я получил UN/PW, я просто сохранил аутентификационную информацию в сеансе. Я интегрировал все в свой существующий код, и все работает так, как должно, и это элегантное решение. - person morgoth84; 29.05.2014

Я бы использовал миксин с представлениями на основе классов django для этого

class AuthenticationMixin(object):
    def dispatch(self, request, *args, **kwargs):
        if not request.user.is_authenticated:
            return HttpResponse('text, test, text', status=401)
        return super(AuthenticationMixin, self).dispatch(request, *args, **kwargs)

Затем вы использовали бы это в представлении следующим образом

class MyView(AuthenticationMixin, DetailView):
.
.
.

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

class MyUnauthenticatedView(DetailView):
.
.
.
person hellsgate    schedule 29.05.2014
comment
Я не знаю, почему вы думаете, что это сработает, но установка локальной переменной в методе __init__ ничего не даст. - person knbk; 29.05.2014
comment
Это потому, что я забыл добавить оператор return. Я также понял, что метод init не подходит для этого, поэтому я исправил это. - person hellsgate; 29.05.2014
comment
Спасибо за ваш вклад. Я уже использую представления на основе классов, я просто не знал, как отправить запрос аутентификации в ответ и как прочитать предоставленные учетные данные, чтобы я мог их аутентифицировать. В итоге мне все это удалось. - person morgoth84; 29.05.2014