Django自帶一個用戶認證系統,這個系統處理用戶賬戶、組、權限和基於cookie的會話,下面將通過分析django源碼的方式仔對該系統進行詳細分析
1. 用戶模型
在django.contrib.auth.models.py包中定義了class User(AbstractUser)類
(1)User模型字段
我在django中使用的是MySql,通過auth_user表查看User字段,當然大家也可以直接通過源碼查看
下面依次對各字段進行說明:
id:用戶ID,主鍵,auto_increment
password:密碼(哈希值,元數據),Django 不儲存原始密碼,原始密碼可以是任意長度的,包含任何字符
last_login:缺省情況下設置為用戶最后一次登錄的日期時間
is_superuser:布爾值,指明用戶擁有所有權限(包括顯式賦予和非顯式賦予的)
username:用戶名,必選項,只能是字母數字(字母、數字和下划線),Changed in Django 1.2: 用戶名現在可以包含 @ 、 + 、 . 和 - 字符
first_name:可選項
last_name:可選項
email:可選項,電子郵件地址
is_staff:布爾值,指明這個用戶是否可以進入管理站點
is_active:指明這個用戶是否是活動的,建議把這個標記設置為False來代替刪除用戶賬戶,這樣就不會影響指向用戶的外鍵。
注意:登錄驗證時不會檢查is_active標志,也就是說這個屬性不控制用戶是否可以登錄,因此,如果在登錄時需要檢查is_active 標志,需要你在自己的登錄視圖中 實現。但是用於 login() 視圖的 AuthenticationForm 函數會執行這個 檢查,因此應當在 Django 站點中進行認證並執行 has_perm() 之類的權限檢查方法,所有那些方法或函數 對於不活動的用戶都會返回 False 。
date_joined:缺省情況下設置為用戶賬戶創建的日期時間
(2)User方法
使用django的shell查看User方法
下面選取主要的方法進行說明:
1)is_anonymous
總是返回False,這是一個區別User和AnonymousUser的方法,通常你會更喜歡用is_authenticated方法
2)is_authenticated
總是返回True,這是一個測試用戶是否經過驗證的方法,這並不表示任何權限,也不測試用戶是否是活動的,這只是驗證用戶是否合法。
3) get_full_name()
返回first_name加上last_name,中間加一個空格
4)set_password(raw_password)
根據原始字符串設置用戶密碼,要注意密碼的哈希算法。不保存 User 對象
5) check_password(raw_password)
6)get_group_permissions(obj=None)
通過用戶的組返回用戶的一套權限字符串,如果有 obj 參數,則只返回這個特定對象的組權限
7)get_all_permissions(obj=None)
通過用戶的組和用戶權限返回用戶的一套權限字符串,如果有 obj 參數,則只返回這個特定對象的組權限
8)has_perm(perm, obj=None)
如果用戶有特定的權限則返回 True ,這里的 perm 的格式為 " label>. codename>",如果用戶是不活動的,這個方法總是返回 False,如果有 obj 參數,這個方法不會檢查模型的權限,只會檢查這個特定對象的 權限
9)has_perms(perm_list, obj=None)
如果用戶有列表中每個特定的權限則返回 True ,這里的 perm 的格式為" label>. codename>" 。如果用戶是不活動的,這個方法 總是返回 False
10)has_module_perms(package_name)
如果用戶在給定的包( Django 應用標簽)中有任何一個權限則返回 True 。如果用戶是不活動的,這個方法總是返回 False
11)email_user(subject, message, from_email=None)
發送一個電子郵件給用戶。如果 from_email 為 None ,則 使用 DEFAULT_FROM_EMAIL
2. 用戶登錄
Django在django.contrib.auth模塊中提供了兩個函數:authenticate和login,在django.contrib.auth.views包中的LoginView類也完美的展示了authenticate和login兩個函數的使用方法,下面先通過源碼分析LoginView類中對這兩個函數的使用,再仔細介紹這兩個函數的實現。
(1)LoginView類實現
先上張圖:
從上圖很清楚的說明了用戶登錄流程:
1)通過AuthenticationForm基類BaseForm的is_valid函數驗證表單信息的合法性,通過clean_xx方法實現,我們會再clean函數中發現使用了authenticate函數
2)通過LoginView的form_valid函數調用auth_login函數(就是django.contrib.auth中的login)實現用戶登錄
(2)authenticate函數
authenticate() 用於驗證指定用戶的用戶名和 密碼。
這個函數有兩個關鍵字參數, username 和 password ,如果密碼與 用戶匹配則返回一個 User 對象,否則返回 None
函數源碼:

def authenticate(request=None, **credentials): """ If the given credentials are valid, return a User object. """ for backend, backend_path in _get_backends(return_tuples=True): try: inspect.getcallargs(backend.authenticate, request, **credentials) except TypeError: # This backend doesn't accept these credentials as arguments. Try the next one. continue try: user = backend.authenticate(request, **credentials) except PermissionDenied: # This backend says to stop in our tracks - this user should not be allowed in at all. break if user is None: continue # Annotate the user object with the path of the backend. user.backend = backend_path return user # The credentials supplied are invalid to all backends, fire signal user_login_failed.send(sender=__name__, credentials=_clean_credentials(credentials), request=request)
(3)login函數
login() 用於在視圖中登錄用戶,它帶有一個 HttpRequest 對象和一個 User 對象,並使用 Django 的會話框架在會話中保存用戶 的 ID
函數源碼:

def login(request, user, backend=None): """ Persist a user id and a backend in the request. This way a user doesn't have to reauthenticate on every request. Note that data set during the anonymous session is retained when the user logs in. """ session_auth_hash = '' if user is None: user = request.user if hasattr(user, 'get_session_auth_hash'): session_auth_hash = user.get_session_auth_hash() if SESSION_KEY in request.session: if _get_user_session_key(request) != user.pk or ( session_auth_hash and not constant_time_compare(request.session.get(HASH_SESSION_KEY, ''), session_auth_hash)): # To avoid reusing another user's session, create a new, empty # session if the existing session corresponds to a different # authenticated user. request.session.flush() else: request.session.cycle_key() try: backend = backend or user.backend except AttributeError: backends = _get_backends(return_tuples=True) if len(backends) == 1: _, backend = backends[0] else: raise ValueError( 'You have multiple authentication backends configured and ' 'therefore must provide the `backend` argument or set the ' '`backend` attribute on the user.' ) else: if not isinstance(backend, str): raise TypeError('backend must be a dotted import path string (got %r).' % backend) request.session[SESSION_KEY] = user._meta.pk.value_to_string(user) request.session[BACKEND_SESSION_KEY] = backend request.session[HASH_SESSION_KEY] = session_auth_hash if hasattr(request, 'user'): request.user = user rotate_token(request) user_logged_in.send(sender=user.__class__, request=request, user=user)
(4)authenticate和login函數使用示例
from django.contrib.auth import authenticate, login def my_view(request): username = request.POST['username'] password = request.POST['password'] user = authenticate(username=username, password=password) if user is not None: if user.is_active: login(request, user) # 重定向到一個登錄成功頁面。 else: # 返回一個“帳戶已禁用”錯誤信息。 else: # 返回一個“非法用戶名或密碼”錯誤信息。
當你手動登錄一個用戶時, 必須 在調用 login() 之前調用 authenticate() 。在成功驗證用戶后,authenticate() 會在 User 上設置一個空屬性,這個信息在以后登錄過程中要用到。
3. 登錄要求裝飾器
(1)login_required函數原型
def login_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME, login_url=None): """ Decorator for views that checks that the user is logged in, redirecting to the log-in page if necessary. """ actual_decorator = user_passes_test( lambda u: u.is_authenticated, login_url=login_url, redirect_field_name=redirect_field_name ) if function: return actual_decorator(function) return actual_decorator
(2)login_required()函數實現以下功能
(a)如果用戶沒有登錄,那么就重定向到settings.LOGIN_URL,並且在查詢字符串中傳遞當前絕對路徑,例如:http://localhost:8000/account/login/?next=/blog/,/blog/為當前訪問頁面
(b)如果用戶已經登錄,則正常的執行視圖,視圖代碼認為用戶已經登錄
(3)login_required()函數參數說明
(a)缺省情況下,成功登陸后重定向的路徑是保存在next參數中,如果想換另外一個名稱,可以使用第二個參數redirect_field_name,如果將redirect_field_name=‘nextlink’,之前的鏈接會變成http://localhost:8000/account/login/?nextlink=/blog/,注意,如果你提供了一個值給 redirect_field_name ,那么你最好也同樣自定義 你的登錄模板。因為保存重定向路徑的模板環境變量會使用redirect_field_name 作為關鍵值,而不使用缺省的 "next"
(b)login_url參數,可選,默認為settings.LOGIN_URL
(4)示例
@login_required(redirect_field_name='nextlink') def blog_title(request): blogs = BlogModel.objects.all() return render(request, 'titles.html', {'blogs':blogs})
4. 用戶登出
在視圖中可以使用 django.contrib.auth.logout() 來登出通過 django.contrib.auth.login() 登錄的用戶。它帶有一個 HttpRequest 對象,沒有返回值
from django.contrib.auth import logout def logout_view(request): logout(request) return redirect('/account/login/') #重定向到另一頁面
當調用 logout() 時,當前請求的會話數據會清空。 這是為了防止另一個用戶使用同一個瀏覽器登錄時會使用到前一個用戶的會話數據。 如果要在用戶登出后在會話中儲存一些數據,那么得在 django.contrib.auth.logout() 之后儲存。注意當用戶沒有登錄時,調用 logout() 函數不會引發任何錯誤。
5. 修改密碼
(1)django提供了python manage.py changepassword *username* 命令修改用戶密碼,如果給定了用戶名,這個命令會提示你輸入兩次密碼。 當兩次輸入的密碼相同時,該用戶的新密碼會立即生效。如果沒有給定用戶,這個命令會 嘗試改變與當前用戶名匹配的用戶的密碼
(2)使用set_password() 方法和 check_password() 函數用於設置和檢查密碼,函數位於django.contrib.auth.base_user.py包中
from django.contrib.auth.models import User u = User.objects.get(username='john') u.set_password('new password') u.save()
(3)Django提供了PasswordChangeView類實現重置密碼,類位於django.contrib.auth.views包中
下面演示如何使用PasswordChangeView類重置密碼:
a. 設置URL
from django.conf.urls import url from django.contrib.auth import views as auth_views urlpatterns = [ url(r'password_change/', auth_views.PasswordChangeView.as_view(), name='password_change'), url(r'password_change/done/', auth_views.PasswordChangeDoneView.as_view(), name='password_change_done'), ]
b. 設置LOGIN_URL = '/account/login/',若不設置會使用global_settings.py中設置的LOGIN_URL = '/accounts/login/'
c. 測試,由於我使用的當前應用為account,啟動瀏覽器輸入localhost:8000/account/password_change/由於未登錄,會轉換到登錄界面,會變成登錄鏈接http://localhost:8000/account/login/?next=/account/password_change/
登錄后,再重置密碼http://localhost:8000/account/password_change/
參考博客: http://blog.chinaunix.net/uid-21633169-id-4352928.html