Django'nun oturum açma bilgilerini varsayılan yapmanın en iyi yolu


103

Büyük çoğunluğuna erişmek için oturum açma gerektiren büyük bir Django uygulaması üzerinde çalışıyorum. Bu, uygulamamızın tamamına serpiştirdiğimiz anlamına gelir:

@login_required
def view(...):

Sorun değil ve her yere eklemeyi hatırladığımız sürece harika çalışıyor ! Ne yazık ki bazen unutuyoruz ve başarısızlık çoğu zaman çok açık değil. Bir görünüme giden tek bağlantı @login_required sayfasındaysa, o görünüme giriş yapmadan gerçekten erişebileceğinizi fark edemezsiniz. Ancak kötü adamlar bunu fark edebilir, ki bu bir problemdir.

Benim fikrim sistemi tersine çevirmekti. Her yerde @login_required yazmak zorunda kalmak yerine, bunun yerine şöyle bir şeyim olur:

@public
def public_view(...):

Sadece halka açık şeyler için. Bunu bazı ara yazılımlarla uygulamaya çalıştım ve işe yarayamadı. Denediğim her şey, kullandığımız diğer ara yazılımlarla kötü bir şekilde etkileşime girdi sanırım. Daha sonra, @ public olmayan her şeyin @login_required olarak işaretlenip işaretlenmediğini kontrol etmek için URL kalıplarını geçmek için bir şeyler yazmayı denedim - en azından o zaman bir şeyi unutursak hızlı bir hata alırız. Ama sonra @login_required'ın bir görünüme uygulanıp uygulanmadığını nasıl anlayacağımı çözemedim ...

Peki bunu yapmanın doğru yolu nedir? Yardım için teşekkürler!


2
Harika soru. Ben de tamamen aynı konumdaydım. Sitenin tamamını login_required yapmak için bir ara yazılımımız var ve farklı kişilere / rollere farklı görünümler / şablon-parçaları göstermek için evde yetiştirilen bir ACL çeşidimiz var, ancak bu her ikisinden de farklı.
Peter Rowell

Yanıtlar:


99

Middleware en iyi bahsiniz olabilir. Bu kod parçasını geçmişte başka bir yerde bulunan bir ön bilgiden değiştirilerek kullandım:

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

Ardından settings.py'de korumak istediğiniz temel URL'leri listeleyin:

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

Siteniz kimlik doğrulaması gerektiren sayfalar için URL kurallarını izlediği sürece bu model çalışacaktır. Bu bire bir uyum değilse, ara katman yazılımını kendi koşullarınıza daha iyi uyacak şekilde değiştirmeyi seçebilirsiniz.

Bu yaklaşımla ilgili sevdiğim şey - kod tabanını @login_requireddekoratörlerle karıştırmanın gerekliliğini ortadan kaldırmanın yanı sıra - kimlik doğrulama şeması değişirse, genel değişiklikler yapmak için gidecek tek bir yeriniz olması.


Teşekkürler, bu harika görünüyor! Ara yazılımımda gerçekten login_required () kullanmak aklıma gelmedi. Sanırım bu, ara yazılım yığınımızla iyi oynadığım soruna geçici bir çözüm bulmaya yardımcı olacak.
samtregar

Doh! Bu neredeyse tam olan bir sayfalar grubu için kullanılan kalıptır vardı HTTPS olmak ve her şey gerekir değil HTTPS. Bu 2,5 yıl önceydi ve tamamen unutmuştum. Thanx, Daniel!
Peter Rowell

4
Ara yazılım RequireLoginMiddleware sınıfı nereye yerleştirilmelidir? views.py, models.py?
Yasin

1
@richard dekoratörleri derleme zamanında çalışır ve bu durumda tek yaptığım şuydu: function.public = True. Daha sonra ara yazılım çalıştığında, erişime izin verilip verilmeyeceğine karar vermek için işlevde .public bayrağını arayabilir. Bu mantıklı değilse, size tam kodu gönderebilirim.
samtregar

1
Bence en iyi yaklaşım, görünümde özniteliği @publicayarlayan dekoratör yapmak _publicve ara katman yazılımı daha sonra bu görünümleri atlamaktır. Django'nun csrf_exempt dekoratörü aynı şekilde çalışır
Ivan Virabyan

31

Her görünüm işlevine bir dekoratör koymanın bir alternatifi vardır. login_required()Dekoratörü de urls.pydosyaya koyabilirsiniz . Bu hala manuel bir görev olsa da, en azından hepsini tek bir yerde tutuyorsunuz, bu da denetlemeyi kolaylaştırıyor.

Örneğin,

    my_views'den home_view içe aktar

    urlpatterns = kalıplar ('',
        # "Ev":
        (r '^ $', login_required (ana_görünüm), dict (şablon_adı = 'sitem / ev.html', öğeler_per_sayfa = 20)),
    )

Görünüm işlevlerinin dize olarak değil, doğrudan adlandırıldığını ve içe aktarıldığını unutmayın.

Ayrıca bunun, sınıflar dahil herhangi bir çağrılabilir görünüm nesnesiyle çalıştığını unutmayın.


3

Django 2.1'de, bir sınıftaki tüm yöntemleri şunlarla dekore edebiliriz :

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'

GÜNCELLEME: Çalışmak için aşağıdakileri de buldum:

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

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

ve set LOGIN_URL = '/accounts/login/'sizin de settings.py


1
bu yeni cevap için teşekkürler. ama lütfen lil biraz açıklayın, resmi dokümanı okusam bile anlayamadım. şimdiden yardım
Tian Loon

@TianLoon lütfen güncellenmiş cevabıma bakın, yardımcı olabilir.
andyandy

2

Url'lerin işlevleri görüntülemek için devredilme biçimini elden geçirmeden Django'daki yerleşik varsayımları değiştirmek zordur.

Django'nun iç kısımlarını karıştırmak yerine, işte kullanabileceğiniz bir denetim. Her bir görünüm işlevini kontrol etmeniz yeterlidir.

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

Bunu çalıştırın ve defuygun dekoratörler olmadan çıktıları inceleyin .


2

İşte django 1.10+ için bir ara yazılım çözümü

Django 1.10+ sürümünde ara yazılımlar yeni bir şekilde yazılmalıdır .

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

Kurulum

  1. Kodu proje klasörünüze kopyalayın ve middleware.py olarak kaydedin
  2. MIDDLEWARE'e ekle

    MIDDLEWARE = ​​[... '.middleware.RequireLoginMiddleware', # Oturum açma gerektir]

  3. Ayarlarınıza ekleyin.py:
LOGIN_REQUIRED_URLS = (
    r'(.*)',
)
LOGIN_REQUIRED_URLS_EXCEPTIONS = (
    r'/admin(.*)$',
)
LOGIN_URL = '/admin'

Kaynaklar:

  1. Daniel Naab'ın bu cevabı

  2. Max Goodridge tarafından hazırlanan Django Middleware öğreticisi

  3. Django Middleware Belgeleri


Hiçbir şey olmamasına rağmen __call__, process_viewkancanın hala kullanıldığını unutmayın [düzenlenmiştir]
Simon Kohlmeyer

1

Ber'in cevabından esinlenerek patterns, tüm URL geri aramalarını login_requireddekoratörle sarmalayarak işlevin yerini alan küçük bir pasaj yazdım . Bu, Django 1.6'da çalışır.

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

Bunu kullanmak şu şekilde çalışır (çağrı listnedeniyle gereklidir yield).

urlpatterns = list(login_required_patterns('', url(r'^$', home_view)))

0

Bunu gerçekten kazanamazsın. Sen basitçe yapmalısın yetkilendirme gereksinimlerinin bir deklarasyon olun. Bu bildirimi doğrudan view işlevi dışında nereye koyarsınız?

Görünüm işlevlerinizi çağrılabilir nesnelerle değiştirmeyi düşünün.

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

Daha sonra görünüm işlevlerinizi LoginViewFunction .

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

my_real_view = MyRealView()  

Herhangi bir kod satırı kaydetmez. Ve "unuttuk" sorununa da yardımcı olmuyor. Tek yapabileceğiniz, görüntüleme işlevlerinin nesneler olduğundan emin olmak için kodu incelemektir. Doğru sınıftan.

Ancak o zaman bile, bir birim test paketi olmadan her görünüm işlevinin doğru olduğunu asla bilemezsiniz .


5
Ben kazanamam Ama kazanmalıyım! Kaybetmek bir seçenek değil! Ama cidden, yetkilendirme gereksinimlerimi bildirmekten kaçınmaya çalışmıyorum. Sadece açıklanması gerekeni tersine çevirmek istiyorum. Tüm özel görünümleri beyan etmek ve herkese açık olanlar hakkında hiçbir şey söylememek yerine, tüm genel görünümleri beyan etmek ve varsayılanın özel olmasını istiyorum.
samtregar

Ayrıca, sınıf olarak görünümler için harika bir fikir ... Ancak bu noktada uygulamamdaki yüzlerce görünümü yeniden yazmanın muhtemelen başlangıç ​​olmadığını düşünüyorum.
samtregar

@samtregar: Kazanmanız mı gerekiyor? Yeni bir Bentley almalıyım. Ciddi anlamda. def'S için grep yapabilirsiniz . defTüm görünüm modüllerindeki hepsini taramak ve @login_required'ın unutulup unutulmadığını belirlemek için önemsiz bir şekilde çok kısa bir komut dosyası yazabilirsiniz .
S.Lott

8
@ S.Lott Bunu yapmanın mümkün olan en kötü yolu bu, ama evet, sanırım işe yarayacak. Hangi tanımların görüş olduğunu nasıl bildiğiniz dışında? Sadece views.py'deki işlevlere bakmak işe yaramaz, yardımcı paylaşılan işlevler @login_required'e ihtiyaç duymaz.
samtregar

Evet, berbat. Neredeyse düşünebildiğim en kötü şey. Hangi tanımların görünümler olduğunu urls.py,.
S. Lot


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.