Django自帶一個用戶認證系統,這個系統處理用戶帳戶、組、權限和基於cookie的會話,下面將經過分析django源碼的方式仔對該系統進行詳細分析
在django.contrib.auth.models.py包中定義了class User(AbstractUser)類html
我在django中使用的是MySql,經過auth_user表查看User字段,固然你們也能夠直接經過源碼查看python
下面依次對各字段進行說明:算法
id:用戶ID,主鍵,auto_incrementshell
password:密碼(哈希值,元數據),Django 不儲存原始密碼,原始密碼能夠是任意長度的,包含任何字符django
last_login:缺省狀況下設置爲用戶最後一次登陸的日期時間瀏覽器
is_superuser:布爾值,指明用戶擁有全部權限(包括顯式賦予和非顯式賦予的)cookie
username:用戶名,必選項,只能是字母數字(字母、數字和下劃線),Changed in Django 1.2: 用戶名如今能夠包含 @ 、 + 、 . 和 - 字符session
first_name:可選項框架
last_name:可選項ide
email:可選項,電子郵件地址
is_staff:布爾值,指明這個用戶是否能夠進入管理站點
is_active:指明這個用戶是不是活動的,建議把這個標記設置爲False來代替刪除用戶帳戶,這樣就不會影響指向用戶的外鍵。
注意:登陸驗證時不會檢查is_active標誌,也就是說這個屬性不控制用戶是否能夠登陸,所以,若是在登陸時須要檢查is_active 標誌,須要你在本身的登陸視圖中 實現。可是用於 login() 視圖的 AuthenticationForm 函數會執行這個 檢查,所以應當在 Django 站點中進行認證並執行 has_perm() 之類的權限檢查方法,全部那些方法或函數 對於不活動的用戶都會返回 False 。
date_joined:缺省狀況下設置爲用戶帳戶建立的日期時間
使用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
Django在django.contrib.auth模塊中提供了兩個函數:authenticate和login,在django.contrib.auth.views包中的LoginView類也完美的展現了authenticate和login兩個函數的使用方法,下面先經過源碼分析LoginView類中對這兩個函數的使用,再仔細介紹這兩個函數的實現。
先上張圖:
從上圖很清楚的說明了用戶登陸流程:
1)經過AuthenticationForm基類BaseForm的is_valid函數驗證表單信息的合法性,經過clean_xx方法實現,咱們會再clean函數中發現使用了authenticate函數
2)經過LoginView的form_valid函數調用auth_login函數(就是django.contrib.auth中的login)實現用戶登陸
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)
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)
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 上設置一個空屬性,這個信息在之後登陸過程當中要用到。
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
(a)若是用戶沒有登陸,那麼就重定向到settings.LOGIN_URL,而且在查詢字符串中傳遞當前絕對路徑,例如:http://localhost:8000/account/login/?next=/blog/,/blog/爲當前訪問頁面
(b)若是用戶已經登陸,則正常的執行視圖,視圖代碼認爲用戶已經登陸
(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
@login_required(redirect_field_name='nextlink') def blog_title(request): blogs = BlogModel.objects.all() return render(request, 'titles.html', {'blogs':blogs})
在視圖中可使用 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() 函數不會引起任何錯誤。
(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