django Admin 로그인 인증 프로세스 소스 분석

19924 단어
우선 로그인 인증도 자신이 정의한 URL 처리임을 명확히 해야 한다. 만약에 프로젝트가django의 Admin을 호출했다면 프로젝트의 URL에서.py에는 반드시 이런 코드가 있을 것이다.
urlpatterns = [
    url(r'^admin/', admin.site.urls),

바로 관리자로 가세요.site.urls가 가서 그 원본 코드는 다음과 같다.
    @property
    def urls(self):
        return self.get_urls(), 'admin', self.name

여기 바로 self를 보세요.get_urls () 함수면 됩니다. 다음 두 개는 url 함수의 매개 변수입니다.get_urls 함수의 소스 코드는 다음과 같습니다.
    def get_urls(self):
        from django.conf.urls import url, include
        #    
        urlpatterns = [
            url(r'^$', wrap(self.index), name='index'),
            url(r'^login/$', self.login, name='login'),

여기에서 우리는 마지막 url(r'^login/$', self.login,name='login'), 즉self를 직접 주목한다.login 함수, 그의 원본 코드는 다음과 같다.
    @never_cache
    def login(self, request, extra_context=None):
        """
        Displays the login form for the given HttpRequest.
        """
        if request.method == 'GET' and self.has_permission(request):
            # Already logged-in, redirect to admin index
            index_path = reverse('admin:index', current_app=self.name)
            return HttpResponseRedirect(index_path)

        from django.contrib.auth.views import login
        # Since this module gets imported in the application's root package,
        # it cannot import models from other applications at the module level,
        # and django.contrib.admin.forms eventually imports User.
        from django.contrib.admin.forms import AdminAuthenticationForm
        context = dict(
            self.each_context(request),
            title=_('Log in'),
            app_path=request.get_full_path(),
            username=request.user.get_username(),
        )
        if (REDIRECT_FIELD_NAME not in request.GET and
                REDIRECT_FIELD_NAME not in request.POST):
            context[REDIRECT_FIELD_NAME] = reverse('admin:index', current_app=self.name)
        context.update(extra_context or {})

        defaults = {
            'extra_context': context,
            'authentication_form': self.login_form or AdminAuthenticationForm,
            'template_name': self.login_template or 'admin/login.html',
        }
        request.current_app = self.name
        return login(request, **defaults)

이 함수 앞에는 상하문 환경의 검측과 준비를 하고 마지막으로django/contrib/auth/views에 들어갑니다.py의 login 함수, 이 함수의 원본 코드는 다음과 같습니다.
@deprecate_current_app
@sensitive_post_parameters()
@csrf_protect
@never_cache
def login(request, template_name='registration/login.html',
          redirect_field_name=REDIRECT_FIELD_NAME,
          authentication_form=AuthenticationForm,
          extra_context=None, redirect_authenticated_user=False):
    """
    Displays the login form and handles the login action.
    """
    redirect_to = request.POST.get(redirect_field_name, request.GET.get(redirect_field_name, ''))

    if redirect_authenticated_user and request.user.is_authenticated:
        redirect_to = _get_login_redirect_url(request, redirect_to)
        if redirect_to == request.path:
            raise ValueError(
                "Redirection loop for authenticated user detected. Check that "
                "your LOGIN_REDIRECT_URL doesn't point to a login page."
            )
        return HttpResponseRedirect(redirect_to)
    elif request.method == "POST":
        form = authentication_form(request, data=request.POST)
        if form.is_valid(): #      
            auth_login(request, form.get_user())  #      
            return HttpResponseRedirect(_get_login_redirect_url(request, redirect_to))
    else:
        form = authentication_form(request)

    current_site = get_current_site(request)

    context = {
        'form': form,
        redirect_field_name: redirect_to,
        'site': current_site,
        'site_name': current_site.name,
    }
    if extra_context is not None:
        context.update(extra_context)

    return TemplateResponse(request, template_name, context)

이 함수 안에서 우리는 form에 중점을 두었다.is_valid () 함수, form=authenticationform,authentication_form은 login 함수의 매개 변수인 AuthenticationForm입니다. 그는forms를 계승합니다.Form,forms.Form은 BaseForm을 상속합니다.그래서 바로 form을 봐요.is_valid 함수는 뭘 했으면 좋겠어요.django/form/forms.py중,isvalid 함수의 소스는 다음과 같습니다.
    def is_valid(self):
        """
        Returns True if the form has no errors. Otherwise, False. If errors are
        being ignored, returns False.
        """
        return self.is_bound and not self.errors

마지막return에서self가 호출된 것을 보았습니다.errors, 그럼 계속해서 errors 함수가 구체적으로 무엇을 했는지 볼까요?errors 소스는 다음과 같습니다.
    @property
    def errors(self):
        "Returns an ErrorDict for the data provided for the form"
        if self._errors is None:
            self.full_clean()
        return self._errors

여기 먼저 self.errors, AuthenticationForm의init_함수 중self.errors = None # Stores the errors after clean () has been called, 그리고 전체 소스에서도self를 호출하는 것을 볼 수 있습니다.full_clean () 이전 self.errors = None이 항상 진실이라면 self를 사용하십시오.full_clean 함수.계속해서 self를 보세요.full_clean의 소스:
    def full_clean(self):
        """
        Cleans all of self.data and populates self._errors and
        self.cleaned_data.
        """
        self._errors = ErrorDict()
        if not self.is_bound:  # Stop further processing.
            return
        self.cleaned_data = {}
        # If the form is permitted to be empty, and none of the form data has
        # changed from the initial data, short circuit any validation.
        if self.empty_permitted and not self.has_changed():
            return

        self._clean_fields()
        self._clean_form() #    
        self._post_clean()

여기 포인트clean_form 함수, 모든 웹 페이지의 로그인은 form 폼을 제출한 다음에 사용자 이름과 비밀번호를 검증하는 것이다.django도 예외가 아니다.self._clean_form ()에서 self가 호출되었습니다.clean 함수, 즉 AuthenticationForm의 clean 함수입니다. AuthenticationForm의 clean 함수 원본을 직접 보십시오.
    def clean(self):
        username = self.cleaned_data.get('username')
        password = self.cleaned_data.get('password')

        if username and password:
            self.user_cache = authenticate(username=username, password=password)#     
            if self.user_cache is None:
                raise forms.ValidationError(
                    self.error_messages['invalid_login'],
                    code='invalid_login',
                    params={'username': self.username_field.verbose_name},
                )
            else:
                self.confirm_login_allowed(self.user_cache)

        return self.cleaned_data

clean 함수에서 마침내 우리가 가장 보고 싶은 논리, 즉 POST에서 사용자 이름과 비밀번호를 얻고 authenticate를 실행하여 로그인 인증을 하는 것을 보았다.(여기에 작은 의문이 하나 있다. self.cleaned data는self.field clean 함수에서 초기화되었지만,self.fields=copy.deepcopy(self.base fields)에서의self.base_fields에서 원본을 찾지 못했습니다)if form에서.is_valid () 함수 실행이 완료되면 auth 에 도착합니다login(request,form.get user()), 여기의form.get_user () 함수는 authenticate가 되돌려준user, 마지막authlogin 함수는 authenticate에서 되돌아오는user를 Request에 부여합니다.사용자,session에 썼습니다. 즉, 이번 요청 사이트의admin에 신분 탭이 있습니다.다음에 이 요청이 로그인이 필요한지 판단할 때 바로 Request를 보십시오.사용자가 존재하는지, 이 사용자가 합법적으로 로그인을 허용하면 ok입니다.구체적으로는django/contrib/admin/sites에 나타난다.py의login 함수,
    @never_cache
    def login(self, request, extra_context=None):
        """
        Displays the login form for the given HttpRequest.
        """
        if request.method == 'GET' and self.has_permission(request):
            # Already logged-in, redirect to admin index
            index_path = reverse('admin:index', current_app=self.name)
            return HttpResponseRedirect(index_path)
          。。。

브라우저 페이지에 만약 폼이 없다면 모든 요청은 get입니다. 우리가 우리의admin 사이트를 직접 요청할 때 get 방법을 사용하기 때문에self만 주목하십시오.has_permission(request): 이 판단은 이 함수에 대한 원본 코드가 다음과 같습니다.
    def has_permission(self, request):
        """
        Returns True if the given HttpRequest has permission to view
        *at least one* page in the admin site.
        """
        return request.user.is_active and request.user.is_staff

그러니까 리퀘스트만.user 객체의 isactive 그리고 isstaff, 그럼 바로 admin의 index로 돌아갑니다.html 인터페이스
그럼 또 하나의 의문이 있습니다, Request.사용자 도대체 언제 얻은 거예요?http://www.jianshu.com/writer#/notebooks/14133407/notes/14917548request 대상은 wsgi 응용 프로그램을 호출할 때 만든 WSGI Request 대상입니다. 처음에 이 대상은 http 요청 정보와 상하문 환경에 대한 봉인된 다음에django의middleware에 매개 변수로 전달하여 처리합니다. 인증이 필요한 프로젝트에는django를 설치해야 합니다.contrib.auth.middleware.Authentication Middleware, 이 middleware 때문에 Request 대상에user 속성이 생겼습니다.이것은django/contrib에서auth.middleware.py의 def processrequest 함수 설명도 알 수 있지만,
    def process_request(self, request):
        # AuthenticationMiddleware is required so that request.user exists.
        if not hasattr(request, 'user'):

이제 본격적으로 Authentication Middleware에 가서 리퀘스트를 찾아보자.사용자, Authentication Middleware의 소스는 다음과 같습니다.
class AuthenticationMiddleware(MiddlewareMixin):
    def process_request(self, request):
        assert hasattr(request, 'session'), (
            "The Django authentication middleware requires session middleware "
            "to be installed. Edit your MIDDLEWARE%s setting to insert "
            "'django.contrib.sessions.middleware.SessionMiddleware' before "
            "'django.contrib.auth.middleware.AuthenticationMiddleware'."
        ) % ("_CLASSES" if settings.MIDDLEWARE is None else "")
        request.user = SimpleLazyObject(lambda: get_user(request)) #    

보셨습니까? 마지막 한마디는 Request를 만드는 것입니다.사용자 등록 정보.그러니까 리퀘스트.user는django 권한 검증 시스템의 기초입니다.그럼 이user 대상을 만드는 근거는 무엇일까요, 어떻게user를 만들었는지는 isactive 그리고 is스태프는요?계속 get사용자 함수:
def get_user(request):
    if not hasattr(request, '_cached_user'):
        request._cached_user = auth.get_user(request) #    
    return request._cached_user

여기는auth에 중점을 두고 있습니다.get_사용자 (request), 원본 보기:django/contrib/auth/init.py
def get_user(request):
    """
    Returns the user model instance associated with the given request session.
    If no user is retrieved an instance of `AnonymousUser` is returned.
    """
    from .models import AnonymousUser
    user = None
    try:
        user_id = _get_user_session_key(request)  #    
        backend_path = request.session[BACKEND_SESSION_KEY] #    
        print "user_id: %s, backend_path: %s" %(user_id, backend_path)
    except KeyError:
        pass
    else:
        if backend_path in settings.AUTHENTICATION_BACKENDS:
            backend = load_backend(backend_path)
            user = backend.get_user(user_id) #    
            print "user attr: ", dir(user)
            # Verify the session
            if hasattr(user, 'get_session_auth_hash'):
                session_hash = request.session.get(HASH_SESSION_KEY)
                session_hash_verified = session_hash and constant_time_compare(
                    session_hash,
                    user.get_session_auth_hash()
                )
                if not session_hash_verified:
                    request.session.flush()
                    user = None

    return user or AnonymousUser()

여기서 우선 user 에 주목해 주세요.id = _get_user_session_key(request),backend_path = request.session[BACKEND SESSION KEY] 두 함수 모두 request에 사용됩니다.session, 그럼 Request.세션 어디서 왔어요?중간부품django.contrib.sessions.middleware.Authentication Middleware, 그리고 Authentication Middleware에서 Authentication Middleware가Session Middleware에 의존하고 settings의 설정에서INSTALLAPPS에서 django를 볼 수 있습니다.contrib.sessions.middleware.SessionMiddleware는django입니다.contrib.auth.middleware.Authentication Middleware, 마찬가지로 원본 코드를 다시 한 번 보고 assert hasattr(request,'session')에 중심을 두면 설명도 볼 수 있습니다.
class AuthenticationMiddleware(MiddlewareMixin):
    def process_request(self, request):
        assert hasattr(request, 'session'), (
            "The Django authentication middleware requires session middleware "
            "to be installed. Edit your MIDDLEWARE%s setting to insert "
            "'django.contrib.sessions.middleware.SessionMiddleware' before "
            "'django.contrib.auth.middleware.AuthenticationMiddleware'."
        ) % ("_CLASSES" if settings.MIDDLEWARE is None else "")
        request.user = SimpleLazyObject(lambda: get_user(request))

그럼 구체적으로 Session Middleware는 뭘 했나요?우리는 원본 코드에서 볼 때 다음과 같다.
class SessionMiddleware(MiddlewareMixin):
    def __init__(self, get_response=None):
        self.get_response = get_response
        engine = import_module(settings.SESSION_ENGINE)
        self.SessionStore = engine.SessionStore

    def process_request(self, request):
        session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME)
        request.session = self.SessionStore(session_key)
        print "request.session._session: ", request.session._session
        print "_session_cache: ", request.session._session_cache

COOKIES에서 세션을 획득함으로써키, 그리고django의session 데이터베이스에서 대응하는session 대상을 찾아 리퀘스트에 값을 부여합니다.세션, 이 세션 대상은 이 세션에 대응하는 로그인 사용자 id를 저장하기 때문에 마지막 get사용자 함수는 Request를 처리합니다.session은 사용자 id를 획득하여 해당하는 AUTH 로부터USER_MODEL이 지정한 데이터베이스에서 대응하는user 대상을 가져와 Request에 값을 부여합니다.사용자가 후속 권한 검증을 진행합니다.(session 및 cookies에 대해서는 다음을 참조하십시오.https://github.com/alsotang/node-lessons/tree/master/lesson16 http://mertensming.github.io/2016/10/19/cookie-session/)
지금, Authentication Middleware와 같은middleware의processRequest는 언제 호출되었습니까?django가 요청을 처리하는 프로세스를 잊지 마십시오.django의 요청 처리 프로세스는 먼저middleware 처리를 통해middleware가response로 되돌아오면 우리가 정의한url->view 처리 프로세스에 가지 않습니다.사실, 모든middleware에는 고정된 방법이 있고, 모든 요청의 처리 절차는Basehandler류의load 를 거친다middleware 함수, 이 함수는middleware에서 정의한 대응하는 함수를 고정된 함수 집합에 불러옵니다.
        self._request_middleware = []
        self._view_middleware = []
        self._template_response_middleware = []
        self._response_middleware = []
        self._exception_middleware = []

그리고 순서대로 요청을 처리한다.더 자세한 것은django/core/handlers/base 참조.py의 loadmiddleware 함수, 기본 코드는 다음과 같습니다.
    def load_middleware(self):
        """
        Populate middleware lists from settings.MIDDLEWARE (or the deprecated
        MIDDLEWARE_CLASSES).

        Must be called after the environment is fixed (see __call__ in subclasses).
        """
        self._request_middleware = []
        self._view_middleware = []
        self._template_response_middleware = []
        self._response_middleware = []
        self._exception_middleware = []

        if settings.MIDDLEWARE is None:
            warnings.warn(
                "Old-style middleware using settings.MIDDLEWARE_CLASSES is "
                "deprecated. Update your middleware and use settings.MIDDLEWARE "
                "instead.", RemovedInDjango20Warning
            )
            handler = convert_exception_to_response(self._legacy_get_response)
            for middleware_path in settings.MIDDLEWARE_CLASSES:
                mw_class = import_string(middleware_path)
                try:
                    mw_instance = mw_class()
                except MiddlewareNotUsed as exc:
                    if settings.DEBUG:
                        if six.text_type(exc):
                            logger.debug('MiddlewareNotUsed(%r): %s', middleware_path, exc)
                        else:
                            logger.debug('MiddlewareNotUsed: %r', middleware_path)
                    continue

                if hasattr(mw_instance, 'process_request'):
                    self._request_middleware.append(mw_instance.process_request)
                if hasattr(mw_instance, 'process_view'):
                    self._view_middleware.append(mw_instance.process_view)
                if hasattr(mw_instance, 'process_template_response'):
                    self._template_response_middleware.insert(0, mw_instance.process_template_response)
                if hasattr(mw_instance, 'process_response'):
                    self._response_middleware.insert(0, mw_instance.process_response)
                if hasattr(mw_instance, 'process_exception'):
                    self._exception_middleware.insert(0, mw_instance.process_exception)
        else:
            handler = convert_exception_to_response(self._get_response)
            for middleware_path in reversed(settings.MIDDLEWARE):
                middleware = import_string(middleware_path)
                try:
                    mw_instance = middleware(handler)
                except MiddlewareNotUsed as exc:
                    if settings.DEBUG:
                        if six.text_type(exc):
                            logger.debug('MiddlewareNotUsed(%r): %s', middleware_path, exc)
                        else:
                            logger.debug('MiddlewareNotUsed: %r', middleware_path)
                    continue

                if mw_instance is None:
                    raise ImproperlyConfigured(
                        'Middleware factory %s returned None.' % middleware_path
                    )

                if hasattr(mw_instance, 'process_view'):
                    self._view_middleware.insert(0, mw_instance.process_view)
                if hasattr(mw_instance, 'process_template_response'):
                    self._template_response_middleware.append(mw_instance.process_template_response)
                if hasattr(mw_instance, 'process_exception'):
                    self._exception_middleware.append(mw_instance.process_exception)

                handler = convert_exception_to_response(mw_instance)

        # We only assign to this when initialization is complete as it is used
        # as a flag for initialization being complete.
        self._middleware_chain = handler

SessionMiddleware가 요청이 들어왔을 때 프로세스를 호출합니다request에 빈session을 만들고 Authorization Middleware에 가서 seesion에 따라 사용자를 가져옵니다. 만약 가져오는 데 실패하면 익명 사용자를 만들고,response 흐름을 되돌릴 때Session Middleware의processresponse, 이 때 Request에 따라.사용자의 인증 상황은session을 저장하거나 삭제합니다.하지만 이곳에 사순환이 일어난 것 같다.만약 이 절차라면, 계속 익명 사용자가 되어서는 안 됩니까?첫 번째 유효한 사용자 검증은 어디에 두었을까요?개인적으로 이것은 프로젝트의 구체적인 배치에 달려 있다.session의 생성과 저장은 Request에 근거합니다.사용자가 하는 일이지만, 전체 요청 절차에서, 우리는 많은 곳에서 Request를 바꿀 수 있습니다.사용자, 그러나 대부분의 교체 작업은 우리가 url에 대응하는 View를 정의하는 것입니다. 예를 들어django가 자체로 가지고 있는 인증 로그인 인터페이스 프로세스에서 로그인 후auth 를 호출합니다.login(request,form.get user() 함수로 바꾸기;아니면django의rest-framework를 바탕으로 자신의 APIview를 정의할 때 프레임워크의Authorization을 호출합니다class는 사용자 인증을 하고 Request를 바꿉니다.user.
요약: 사용자가 Admin에 처음 로그인했을 때 Request.COOKIES에 sessionid가 없으므로 auth/init.py의 getuser 함수는 AnonymousUser () 대상을 되돌려줍니다. 그의 isstaff와 isactive는 모두false이기 때문에admin에 방문하면login, 즉 로그인 인터페이스로 바뀌고 사용자 이름과 비밀번호를 제출하여 로그인합니다. form에 있습니다.is_valid () 함수에서 제출한 사용자 이름과 비밀번호를 settings에서 지정한 AUTHENTICATIONBACKENDS에서 검증하고 성공하면 admin의 index로 다시 지정합니다.html인터페이스.다음에 admin 인터페이스를 다시 방문할 때 리퀘스트의 cookies에sessionid가 있기 때문에Session Middleware는 이sessionid에 따라session 데이터베이스에서 대응하는session의 값을 리퀘스트에 부여합니다.session, Authentication Middleware 처리 Request.session은 사용자, 즉 이번 방문에 대응하는 사용자를 가져와 Request에 값을 부여합니다.user는 다음에 판단하고 호출할 수 있습니다.login 함수에서 Request.user 진행 isstaff와 isactive 판단, 최종 결정은admin의 index로 바꿉니다.html 아니면 로그인 인터페이스.

좋은 웹페이지 즐겨찾기