首先要明確,登陸認(rèn)證也是自己定義的對(duì)url的處理,如果一個(gè)項(xiàng)目調(diào)用了django的Admin,那么在project的urls.py中一定會(huì)有這樣一段代碼:
urlpatterns = [
url(r'^admin/', admin.site.urls),
直接到admin.site.urls去看看,其源代碼如下:
@property
def urls(self):
return self.get_urls(), 'admin', self.name
這里直接看self.get_urls()函數(shù)就好了,后面兩個(gè)是url函數(shù)的參數(shù)。get_urls函數(shù)的源代碼如下:
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'),
這里我們直接關(guān)注最后一句 url(r'^login/$', self.login, name='login'),也就是self.login函數(shù),他的源碼如下:
@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)
這個(gè)函數(shù)前面是做一些上下文環(huán)境的檢測(cè)和準(zhǔn)備,最后真正進(jìn)入django/contrib/auth/views.py中的login函數(shù),這個(gè)函數(shù)的源碼如下:
@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(): #重點(diǎn)關(guān)注這里
auth_login(request, form.get_user()) #重點(diǎn)關(guān)注這里
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)
在這函數(shù)里面,我們重點(diǎn)關(guān)注form.is_valid()函數(shù),form=authentication_form,authentication_form就是login函數(shù)的參數(shù)AuthenticationForm,他繼承自forms.Form,forms.Form繼承BaseForm。所以,直接看form.is_valid函數(shù)做了什么就好了。
在django/form/forms.py中,is_valid函數(shù)的源碼如下:
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中調(diào)用了self.errors,那么繼續(xù)看errors函數(shù)具體做了什么呢?,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_函數(shù)中有self._errors = None # Stores the errors after clean() has been called,而且我們?cè)谡麄€(gè)源碼中也可以看到,在調(diào)用self.full_clean()之前self._errors = None一直為真,那么就調(diào)用self.full_clean函數(shù)。繼續(xù)看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() #重點(diǎn)關(guān)注
self._post_clean()
這里重點(diǎn)關(guān)注_clean_form函數(shù),每個(gè)網(wǎng)頁(yè)的登陸都是通過提交form表單,然后驗(yàn)證用戶名和密碼的,django也不例外。self._clean_form()中調(diào)用了self.clean函數(shù),也就是AuthenticationForm的clean函數(shù),直接看AuthenticationForm的clean函數(shù)源碼:
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)# 重點(diǎn)關(guān)注
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函數(shù)里面終于見到了我們最想看到的邏輯,也就是從POST中獲取用戶名和密碼,然后執(zhí)行authenticate進(jìn)行登錄認(rèn)證。(這里有個(gè)小疑問,self.cleaned_data雖然在self.field_clean函數(shù)中有初始化,但是self.fields = copy.deepcopy(self.base_fields)中的self.base_fields一直沒找到來(lái)源)
在 if form.is_valid()函數(shù)執(zhí)行完成后,就到了 auth_login(request, form.get_user()) ,這里的form.get_user()函數(shù)返回的就是authenticate返回的user,最后auth_login函數(shù)將authenticate返回的user賦值給request.user,并寫到session中,也就是這次請(qǐng)求站點(diǎn)的admin時(shí)就有了身份標(biāo)簽。下次判斷這個(gè)請(qǐng)求是否需要登陸的時(shí)候直接就是看request.user是否存在,并且這個(gè)user是合法的被允許登陸的就ok了。具體體現(xiàn)在,django/contrib/admin/sites.py中的login函數(shù),
@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)
后面的省略。。。
瀏覽器頁(yè)面若果沒有表單,所有的請(qǐng)求一般都是get,我們直接請(qǐng)求我們的admin站點(diǎn)的時(shí)候就是用的get方法,所以只需要關(guān)注self.has_permission(request):這個(gè)判斷,關(guān)于這個(gè)函數(shù)的源碼如下:
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
所以說,只要request.user對(duì)象的is_active而且is_staff,那么就直接返回到了admin的index.html界面
那么又有一個(gè)疑問了,request.user到底是什么時(shí)候得來(lái)的,這里就要參照《django框架在正式環(huán)境中的請(qǐng)求流程分析》一文了http://m.itdecent.cn/writer#/notebooks/14133407/notes/14917548
request對(duì)象是在調(diào)用wsgi應(yīng)用的時(shí)候創(chuàng)建的一個(gè)WSGIRequest對(duì)象,一開始這個(gè)對(duì)象是對(duì)http請(qǐng)求信息,以及上下文環(huán)境的封裝,然后作為參數(shù)傳遞給django的middleware去處理,對(duì)于需要認(rèn)證的project,一定要安裝django.contrib.auth.middleware.AuthenticationMiddleware,也正是因?yàn)檫@個(gè)middleware,使得request對(duì)象有了user屬性。這個(gè)可以從django/contrib.auth.middleware.py中的def process_request函數(shù)說明也可以看出,
def process_request(self, request):
# AuthenticationMiddleware is required so that request.user exists.
if not hasattr(request, 'user'):
現(xiàn)在我們正式的去AuthenticationMiddleware里面找一下request.user,AuthenticationMiddleware的源碼如下:
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)) #重點(diǎn)關(guān)注
看到了么,最后一句就是創(chuàng)建request.user屬性。所以說,request.user是django權(quán)限驗(yàn)證系統(tǒng)的基礎(chǔ)。那創(chuàng)建這個(gè)user對(duì)象的依據(jù)又是什么呢,怎樣創(chuàng)建的user他是is_active而且是is_staff呢?繼續(xù)看get_user函數(shù):
def get_user(request):
if not hasattr(request, '_cached_user'):
request._cached_user = auth.get_user(request) #重點(diǎn)關(guān)注
return request._cached_user
這里重點(diǎn)關(guān)注auth.get_user(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) #重點(diǎn)注意
backend_path = request.session[BACKEND_SESSION_KEY] #重點(diǎn)注意
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) #重點(diǎn)注意
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()
這里先重點(diǎn)關(guān)注user_id = _get_user_session_key(request),backend_path = request.session[BACKEND_SESSION_KEY]兩個(gè)函數(shù),這兩個(gè)函數(shù)都要用到request.session,那么request.session從哪兒來(lái)的呢?從中間件django.contrib.sessions.middleware.SessionMiddleware,而且在AuthenticationMiddleware中很明顯的說明了AuthenticationMiddleware依賴于SessionMiddleware,而且在settings的配置中INSTALL_APPS中,可以看到django.contrib.sessions.middleware.SessionMiddleware是在django.contrib.auth.middleware.AuthenticationMiddleware,同樣,再次看一下源碼,重點(diǎn)注意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))
那具體SessionMiddleware做了什么呢?我們依據(jù)從源碼中看起:
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中的session_key,然后從django的session數(shù)據(jù)庫(kù)中尋找對(duì)應(yīng)的session對(duì)象,賦值給request.session,這個(gè)seesion對(duì)象保存著這個(gè)session對(duì)應(yīng)的登陸用戶id,所以最后get_user函數(shù)通過處理request.session獲得用戶id,進(jìn)而從對(duì)應(yīng)的AUTH_USER_MODEL指定的數(shù)據(jù)庫(kù)中獲取對(duì)應(yīng)的user對(duì)象,賦值給request.user進(jìn)行后續(xù)的權(quán)限驗(yàn)證。(關(guān)于session和cookies,參見:
https://github.com/alsotang/node-lessons/tree/master/lesson16
http://mertensming.github.io/2016/10/19/cookie-session/
)
現(xiàn)在,很有可能現(xiàn)在又冒出一個(gè)疑問,像AuthenticationMiddleware這些middleware的process_request是什么時(shí)候調(diào)用的呢?不要忘記django處理請(qǐng)求的流程,django的請(qǐng)求處理流程是先通過middleware處理,middleware如果返回了response,那么就不會(huì)走到我們定義的url->view處理流程的。其實(shí),每個(gè)middleware中都有固定的一類方法,并且每個(gè)請(qǐng)求的處理流程都會(huì)經(jīng)過Basehandler類的load_middleware函數(shù),這個(gè)函數(shù)將middleware中定義的對(duì)應(yīng)的函數(shù)裝載到一個(gè)固定的函數(shù)集合,他們分別是
self._request_middleware = []
self._view_middleware = []
self._template_response_middleware = []
self._response_middleware = []
self._exception_middleware = []
然后按順序?qū)φ?qǐng)求做處理。更詳細(xì)的參見django/core/handlers/base.py中的load_middleware函數(shù),起源碼如下:
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在請(qǐng)求進(jìn)來(lái)的時(shí)候調(diào)用process_request時(shí)候創(chuàng)建一個(gè)空的session,然后到AuthorizationMiddleware去根據(jù)seesion獲取用戶,如果獲取失敗,就創(chuàng)建一個(gè)匿名用戶,然后在返回response流的時(shí)候調(diào)用SessionMiddleware的process_response,此時(shí)會(huì)根據(jù)request.user的認(rèn)證情況去保存或者銷毀session。 但是這里好像出現(xiàn)了一個(gè)死循環(huán)。如果是這個(gè)流程,那不得一直是匿名用戶?第一個(gè)有效的用戶驗(yàn)證是放在哪兒的呢? 個(gè)人理解,這個(gè)得看項(xiàng)目的具體部署。session的創(chuàng)建和保存,是根據(jù)request.user去做的,而在整個(gè)請(qǐng)求流程中,我們可以在很多地方去替換request.user,不過大多數(shù)替換工作都是在我們自己定義url對(duì)應(yīng)的的View中,比如django自帶的認(rèn)證登錄界面流程中,在登陸后調(diào)用auth_login(request, form.get_user())函數(shù)去替換;或者我們?cè)诨赿jango的rest-framework定義自己的APIview的時(shí)候去調(diào)用框架的Authorization_class去做用戶認(rèn)證,然后替換request.user。
總結(jié):
當(dāng)用戶第一次登陸Admin的時(shí)候,request.COOKIES中沒有sessionid,所以auth/init.py中的get_user函數(shù)返回的是一個(gè)AnonymousUser()對(duì)象,他的is_staff和is_active都是False,所以訪問admin就被重定向到了login,也就是登陸界面,然后提交用戶名和密碼登陸,在form.is_valid()函數(shù)里面對(duì)提交的用戶名和密碼在settings中指定的AUTHENTICATION_BACKENDS中驗(yàn)證,如果成功則重定向到admin的index.html界面。當(dāng)下次再一次訪問admin界面的時(shí)候,因?yàn)閞equest中的cookies中有sessionid, SessionMiddleware根據(jù)這個(gè)sessionid從session數(shù)據(jù)庫(kù)中拿到對(duì)應(yīng)的session賦值給request.session,AuthenticationMiddleware處理request.session獲取對(duì)應(yīng)user,也就是此次訪問的user,并賦值給request.user供下次判斷和調(diào)用。在login函數(shù)里面對(duì)request.user進(jìn)行is_staff和is_active判斷,最后決定是重定向到admin的index.html還是登陸界面。