Лучший способ сделать Django login_required по умолчанию - PullRequest
95 голосов
/ 29 января 2010

Я работаю над большим приложением Django, подавляющему большинству которого для входа требуется логин. Это означает, что во всем приложении мы добавили:

@login_required
def view(...):

Это прекрасно, и это прекрасно работает , пока мы не забываем добавлять его везде ! К сожалению, иногда мы забываем, и неудача часто не очень очевидна. Если единственная ссылка на представление находится на странице @login_required, то вы вряд ли заметите, что на самом деле можете достичь этого представления без входа в систему. Но плохие парни могут заметить, что является проблемой.

Моя идея состояла в том, чтобы полностью изменить систему. Вместо того, чтобы набирать @login_required везде, вместо этого у меня будет что-то вроде:

@public
def public_view(...):

Просто для публики. Я попытался реализовать это с помощью некоторого промежуточного программного обеспечения, и я не мог заставить его работать. Думаю, все, что я пробовал, плохо взаимодействовало с другим промежуточным программным обеспечением, которое мы используем. Затем я попытался написать что-то, чтобы просмотреть шаблоны URL, чтобы убедиться, что все, что не является @public, помечено @login_required - по крайней мере, тогда мы получим быструю ошибку, если забудем что-нибудь. Но тогда я не мог понять, как определить, применен ли @login_required к представлению ...

Итак, как правильно это сделать? Спасибо за помощь!

Ответы [ 8 ]

93 голосов
/ 29 января 2010

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

import re

from django.conf import settings
from django.contrib.auth.decorators import login_required


class RequireLoginMiddleware(object):
    """
    Middleware component that wraps the login_required decorator around
    matching URL patterns. To use, add the class to MIDDLEWARE_CLASSES and
    define LOGIN_REQUIRED_URLS and LOGIN_REQUIRED_URLS_EXCEPTIONS in your
    settings.py. For example:
    ------
    LOGIN_REQUIRED_URLS = (
        r'/topsecret/(.*)$',
    )
    LOGIN_REQUIRED_URLS_EXCEPTIONS = (
        r'/topsecret/login(.*)$',
        r'/topsecret/logout(.*)$',
    )
    ------
    LOGIN_REQUIRED_URLS is where you define URL patterns; each pattern must
    be a valid regex.

    LOGIN_REQUIRED_URLS_EXCEPTIONS is, conversely, where you explicitly
    define any exceptions (like login and logout URLs).
    """
    def __init__(self):
        self.required = tuple(re.compile(url) for url in settings.LOGIN_REQUIRED_URLS)
        self.exceptions = tuple(re.compile(url) for url in settings.LOGIN_REQUIRED_URLS_EXCEPTIONS)

    def process_view(self, request, view_func, view_args, view_kwargs):
        # No need to process URLs if user already logged in
        if request.user.is_authenticated():
            return None

        # An exception match should immediately return None
        for url in self.exceptions:
            if url.match(request.path):
                return None

        # Requests matching a restricted URL pattern are returned
        # wrapped with the login_required decorator
        for url in self.required:
            if url.match(request.path):
                return login_required(view_func)(request, *view_args, **view_kwargs)

        # Explicitly return None for all non-matching requests
        return None

Затем в settings.py перечислите базовые URL, которые вы хотите защитить:

LOGIN_REQUIRED_URLS = (
    r'/private_stuff/(.*)$',
    r'/login_required/(.*)$',
)

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

Что мне нравится в этом подходе - помимо устранения необходимости засорять кодовую базу с помощью @login_required декораторов - то, что если схема аутентификации изменяется, у вас есть одно место, чтобы сделать глобальные изменения.

29 голосов
/ 30 января 2010

Существует альтернатива установке декоратора на каждую функцию просмотра. Вы также можете поместить декоратор login_required() в файл urls.py. Хотя это все еще ручное задание, по крайней мере, у вас есть все это в одном месте, что облегчает аудит.

например.,

    from my_views import home_view

    urlpatterns = patterns('',
        # "Home":
        (r'^$', login_required(home_view), dict(template_name='my_site/home.html', items_per_page=20)),
    )

Обратите внимание, что функции представления именуются и импортируются напрямую, а не в виде строк.

Также обратите внимание, что это работает с любым вызываемым объектом вида, включая классы.

2 голосов
/ 27 октября 2017

Вот промежуточное решение для django 1.10 +

Промежуточное программное обеспечение в должно быть написано по-новому в django 1.10 + .

Код

import re

from django.conf import settings
from django.contrib.auth.decorators import login_required


class RequireLoginMiddleware(object):

    def __init__(self, get_response):
         # One-time configuration and initialization.
        self.get_response = get_response

        self.required = tuple(re.compile(url)
                              for url in settings.LOGIN_REQUIRED_URLS)
        self.exceptions = tuple(re.compile(url)
                                for url in settings.LOGIN_REQUIRED_URLS_EXCEPTIONS)

    def __call__(self, request):

        response = self.get_response(request)
        return response

    def process_view(self, request, view_func, view_args, view_kwargs):

        # No need to process URLs if user already logged in
        if request.user.is_authenticated:
            return None

        # An exception match should immediately return None
        for url in self.exceptions:
            if url.match(request.path):
                return None

        # Requests matching a restricted URL pattern are returned
        # wrapped with the login_required decorator
        for url in self.required:
            if url.match(request.path):
                return login_required(view_func)(request, *view_args, **view_kwargs)

        # Explicitly return None for all non-matching requests
        return None

Установка

  1. Скопируйте код в папку вашего проекта и сохраните как middleware.py
  2. Добавить в СРЕДНЕЕ ОБОРУДОВАНИЕ

    MIDDLEWARE = ​​[ ... '.middleware.RequireLoginMiddleware', # Требовать логин ]

  3. Добавить в настройки.py:
LOGIN_REQUIRED_URLS = (
    r'(.*)',
)
LOGIN_REQUIRED_URLS_EXCEPTIONS = (
    r'/admin(.*)$',
)
LOGIN_URL = '/admin'

Источники:

  1. Этот ответ от Daniel Naab

  2. Учебное пособие по промежуточному программному обеспечению Django от Макс Гудридж

  3. Django Middleware Docs

2 голосов
/ 29 января 2010

Трудно изменить встроенные предположения в Django, не переделывая способ передачи URL-адресов для просмотра функций.

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

import os
import re

def view_modules( root ):
    for path, dirs, files in os.walk( root ):
        for d in dirs[:]:
            if d.startswith("."):
                dirs.remove(d)
        for f in files:
            name, ext = os.path.splitext(f)
            if ext == ".py":
                if name == "views":
                    yield os.path.join( path, f )

def def_lines( root ):
    def_pat= re.compile( "\n(\S.*)\n+(^def\s+.*:$)", re.MULTILINE )
    for v in view_modules( root ):
        with open(v,"r") as source:
            text= source.read()
            for p in def_pat.findall( text ):
                yield p

def report( root ):
    for decorator, definition in def_lines( root ):
        print decorator, definition

Запустите это и проверьте вывод на def с без соответствующих декораторов.

1 голос
/ 07 сентября 2018

В Django 2.1 мы можем декорировать все методы в классе с помощью:

from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView

@method_decorator(login_required, name='dispatch')
class ProtectedView(TemplateView):
    template_name = 'secret.html'

UPDATE: Я также нашел следующие работы:

from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import TemplateView

class ProtectedView(LoginRequiredMixin, TemplateView):
    template_name = 'secret.html'

и установите LOGIN_URL = '/accounts/login/' в ваших settings.py

1 голос
/ 19 апреля 2014

Вдохновленный ответом Бер, я написал небольшой фрагмент, который заменяет функцию patterns, оборачивая все обратные вызовы URL декоратором login_required. Это работает в Django 1.6.

def login_required_patterns(*args, **kw):
    for pattern in patterns(*args, **kw):
        # This is a property that should return a callable, even if a string view name is given.
        callback = pattern.callback

        # No property setter is provided, so this will have to do.
        pattern._callback = login_required(callback)

        yield pattern

Использование этого работает так (вызов list необходим из-за yield).

urlpatterns = list(login_required_patterns('', url(r'^$', home_view)))
0 голосов
/ 27 октября 2017

Можно было бы иметь единственную отправную точку для всех urls в некотором роде включений, которые бы украшали его с помощью этих пакетов https://github.com/vorujack/decorate_url.

0 голосов
/ 29 января 2010

Вы не можете действительно выиграть это. Вы просто должны сделать заявление об авторизации. Куда бы вы еще поместили это объявление, кроме функции вида?

Рассмотрите возможность замены функций просмотра вызываемыми объектами.

class LoginViewFunction( object ):
    def __call__( self, request, *args, **kw ):
        p1 = self.login( request, *args, **kw )
        if p1 is not None:
            return p1
        return self.view( request, *args, **kw )
    def login( self, request )
        if not request.user.is_authenticated():
            return HttpResponseRedirect('/login/?next=%s' % request.path)
    def view( self, request, *args, **kw ):
        raise NotImplementedError

Затем вы делаете ваши функции представления подклассами LoginViewFunction.

class MyRealView( LoginViewFunction ):
    def view( self, request, *args, **kw ):
        .... the real work ...

my_real_view = MyRealView()  

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

Но даже в этом случае вы никогда не узнаете, что каждая функция просмотра корректна без набора тестов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...