Najlepszy sposób, aby logowanie Django wymagało domyślnego

Pracuję nad dużą aplikacją Django, z której zdecydowana większość wymaga logowania, aby uzyskać dostęp. Oznacza to, że w całej naszej aplikacji posypaliśmy:

@login_required
def view(...):

To dobrze, i działa świetnie tak długo, jak pamiętamy, aby dodać go wszędzie ! Niestety czasami zapominamy, a niepowodzenie często nie jest strasznie widoczne. Jeśli jedyny link do widoku znajduje się na stronie @login_required, prawdopodobnie nie zauważysz, że możesz dotrzeć do tego widoku bez logowania. Ale źli mogą zauważyć, co jest problemem.

Mój pomysł polegał na odwróceniu systemu. Zamiast wpisywać @ login_required Wszędzie, zamiast tego miałbym coś w stylu:
@public
def public_view(...):
Tylko dla spraw publicznych. Próbowałem zaimplementować to za pomocą oprogramowania pośredniczącego i nie mogłem go uruchomić. Wszystko, czego próbowałem, źle oddziaływało na inne oprogramowanie, z którego korzystamy. Następnie próbowałem napisać coś, aby przejść wzorce URL, aby sprawdzić, czy wszystko, co nie jest @public został oznaczony @login_required - przynajmniej wtedy dostalibyśmy szybki błąd, gdybyśmy czegoś zapomnieli. Ale wtedy nie mogłem dowiedzieć się, jak stwierdzić, czy @login_required został zastosowany do widoku... Więc jak to zrobić? Dzięki za pomoc!
 104
Author: samtregar, 2010-01-29

10 answers

Middleware może być najlepszym rozwiązaniem. Używałem tego fragmentu kodu w przeszłości, zmodyfikowanego z fragmentu znalezionego gdzie indziej:

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

Następnie w settings.py, lista bazowych adresów URL, które chcesz chronić:

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

Tak długo, jak twoja witryna działa zgodnie z konwencjami URL dla stron wymagających uwierzytelniania, ten model będzie działał. Jeśli nie jest to dopasowanie Jeden do jednego, możesz zmodyfikować oprogramowanie pośredniczące, aby bardziej dopasować je do twoich okoliczności.

Co lubię w tym podejściu-poza usunięcie konieczności zaśmiecania bazy kodowej dekoratorami @login_required - polega na tym, że jeśli schemat uwierzytelniania się zmieni, masz jedno miejsce, aby dokonać globalnych zmian.

 100
Author: Daniel Naab,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-27 18:10:34

Istnieje alternatywa dla umieszczenia dekoratora na każdej funkcji widoku. Możesz również umieścić dekorator login_required() w pliku urls.py. Chociaż jest to nadal zadanie ręczne, przynajmniej masz to wszystko w jednym miejscu, co ułatwia kontrolę.

Np.,

    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)),
    )

Zauważ, że funkcje widoku są nazwane i importowane bezpośrednio, a nie jako ciągi znaków.

Zauważ również, że działa to z dowolnym wywoływalnym obiektem widoku, w tym z klasami.

 32
Author: Ber,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-10 23:33:57

W Django 2.1 możemy ozdobić wszystkie metody w klasie za pomocą:

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'

Aktualizacja: Znalazłem również następujące do pracy:

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

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

I ustaw LOGIN_URL = '/accounts/login/' w swoim settings.py

 3
Author: andyandy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-12-17 17:36:40

Trudno jest zmienić wbudowane założenia w Django bez przeróbki sposobu przekazywania adresów url do wyświetlania funkcji.

Zamiast rozprawiać się z wewnętrznymi Django, oto audyt, którego możesz użyć. Wystarczy sprawdzić każdą funkcję widoku.

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

Uruchom to i sprawdź wyjście dla defs bez odpowiednich dekoratorów.

 2
Author: S.Lott,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-29 20:17:37

Oto rozwiązanie middleware dla django 1.10 +

Middleware w muszą być napisane w nowy sposób w django 1.10 + .

Kod

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

Instalacja

  1. skopiuj kod do folderu projektu i zapisz jako middleware.py
  2. Dodaj do MIDDLEWARE

    MIDDLEWARE = [ ... '.middleware.RequireLoginMiddleware', # Require login ]

  3. Dodaj do swojego settings.py:
LOGIN_REQUIRED_URLS = (
    r'(.*)',
)
LOGIN_REQUIRED_URLS_EXCEPTIONS = (
    r'/admin(.*)$',
)
LOGIN_URL = '/admin'

Źródła:

  1. Ta ODPOWIEDŹ by Daniel Naab

  2. Django Middleware tutorial by Max Goodridge

  3. Django Middleware Docs

 2
Author: np8,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-21 07:01:33

Zainspirowany odpowiedzią Bera napisałem mały fragment, który zastępuje funkcję patterns, owijając wszystkie wywołania zwrotne URL dekoratorem login_required. Działa to w 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

Używanie go działa w ten sposób (wywołanie do {[4] } jest wymagane ze względu na yield).

urlpatterns = list(login_required_patterns('', url(r'^$', home_view)))
 1
Author: rectangletangle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-22 03:58:02

Jest aplikacja, która zapewnia rozwiązanie plug-and-play:

Https://github.com/mgrouchy/django-stronghold

pip install django-stronghold
# settings.py

INSTALLED_APPS = (
    #...
    'stronghold',
)

MIDDLEWARE_CLASSES = (
    #...
    'stronghold.middleware.LoginRequiredMiddleware',
)
 1
Author: getup8,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-04-20 22:50:34

Nie możesz tego wygrać. Po prostu musisz złożyć deklarację wymagań autoryzacji. Gdzie indziej można umieścić tę deklarację, z wyjątkiem funkcji widoku?

Rozważ zastąpienie funkcji widoku obiektami do wywołania.

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

Następnie tworzysz podklasy funkcji widoku z LoginViewFunction.

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

my_real_view = MyRealView()  

Nie zapisuje żadnych linii kodu. I to nie pomaga w problemie "zapomnieliśmy". Wszystko, co możesz zrobić, to sprawdzić kod, aby upewnić się, że funkcje widoku są obiektami. Odpowiedniej klasy.

Ale nawet wtedy, nigdy nie dowiesz się, że Każda Funkcja widoku jest poprawna bez zestawu testów jednostkowych.

 0
Author: S.Lott,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-29 18:29:43

Byłoby możliwe, aby mieć jeden punkt wyjścia dla wszystkich urls w rodzaju include i że ozdobić go za pomocą tego pakietu https://github.com/vorujack/decorate_url .

 0
Author: rootart,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-27 14:42:17

Począwszy od Django 3+, możesz zmienić domyślne ustawienia jak poniżej:

Krok 1: Utwórz nowy plik anything.py w Twoim katalogu yourapp i napisz co następuje:

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

//for registering a class as middleware you at least __init__() and __call__()
//for this case we additionally need process_view() which will be automatically called by Django before rendering a view/template

class ClassName(object):
    
    //need for one time initialization, here response is a function which will be called to get response from view/template
    def __init__(self, response):
        self.get_response = response
        self.required = tuple(re.compile(url) for url in settings.AUTH_URLS)
        self.exceptions = tuple(re.compile(url)for url in settings.NO_AUTH_URLS)

    def __call__(self, request):
        //any code written here will be called before requesting response
        response = self.get_response(request)
        //any code written here will be called after response
        return response

    //this is called before requesting response
    def process_view(self, request, view_func, view_args, view_kwargs):
        //if authenticated return no exception
        if request.user.is_authenticated:
            return None
                
        //default case, no exception
        return login_required(view_func)(request, *view_args, **view_kwargs)

Krok 2: dodaj to anything.py do Middleware [] w project/settings.py like following

MIDDLEWARE = [
    // your previous middleware
    'yourapp.anything.ClassName',
]
 0
Author: Mahbub Alam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-11-01 14:44:28