在前面咱們已經完成了,前端登陸頁面的搭建,以及路由分配,如今咱們做關於登陸認證部分的東西javascript
Django提供了認證系統。認證系統包含:前端
用戶vue
權限:二元(是/否)標誌指示一個用戶是否能夠作一個特定的任務。java
組:對多個用戶運用標籤和權限的一種通用的方式。python
一個可配置的密碼哈希系統ios
用戶登陸或內容顯示的表單和視圖git
一個可插拔的後臺系統github
Django默認用戶的認證機制依賴Session機制,咱們在本項目中將引入JWT認證機制,將用戶的登錄狀態[認證信息]存放在Token字符串中,而後對接Django的認證系統,幫助咱們來實現:算法
用戶的數據模型數據庫
用戶密碼的加密與驗證
用戶的權限系統
Django認證系統中提供了用戶模型類User保存用戶的數據,默認的User包含如下常見的基本字段:
username
必選。 150個字符之內。 用戶名可能包含字母數字,_
,@
,+
.
和-
個字符。在Django更改1.10:max_length
從30個字符增長到150個字符。
email
可選(blank=True
)。 郵箱地址。
password
必選。 密碼的哈希及元數據。 (Django 不保存原始密碼)。 原始密碼能夠無限長並且能夠包含任意字符。
groups
與Group
之間的多對多關係。
user_permissions
與Permission
之間的多對多關係。
is_staff
布爾值。 指示用戶是否能夠訪問Admin 站點。
is_active
布爾值。 指示用戶的帳號是否激活。 咱們建議您將此標誌設置爲False
而不是刪除賬戶;這樣,若是您的應用程序對用戶有任何外鍵,則外鍵不會中斷。它不是用來控制用戶是否可以登陸。 在Django更改1.10:在舊版本中,默認is_active爲False不能進行登陸。
is_superuser
布爾值。 指定這個用戶擁有全部的權限而不須要給他們分配明確的權限。
last_login
用戶最後一次登陸的時間。
date_joined
帳戶建立的時間。 當帳號建立時,默認設置爲當前的date/time。
set_password
(raw_password)
設置用戶的密碼爲給定的原始字符串,並負責密碼的。 不會保存User
對象。當None
爲raw_password
時,密碼將設置爲一個不可用的密碼。
check_password
(raw_password)
若是給定的raw_password是用戶的真實密碼,則返回True,能夠在校驗用戶密碼時使用。
管理器方法便可以經過User.objects.
進行調用的方法。
create_user
(username, email=None, password=None, **extra_fields)
建立、保存並返回一個User
對象。
create_superuser
(username, email, password, **extra_fields)
與create_user()
相同,可是設置is_staff
和is_superuser
爲True
。
Django認證系統中提供的用戶模型類及方法很方便,咱們可使用這個模型類,可是字段有些沒法知足項目需求,如本項目中須要保存用戶的手機號,須要給模型類添加額外的字段。
Django提供了django.contrib.auth.models.AbstractUser
用戶抽象模型類容許咱們繼承,擴展字段來使用Django認證系統的用戶模型類。
咱們能夠在apps中建立Django應用users,並在配置文件中註冊users應用。
在建立好的應用models.py中定義用戶的用戶模型類。
class User(AbstractUser): """用戶模型類""" mobile = models.CharField(max_length=11, unique=True, verbose_name='手機號') class Meta: db_table = 'ly_users' verbose_name = '用戶' verbose_name_plural = verbose_name
咱們自定義的用戶模型類還不能直接被Django的認證系統所識別,須要在配置文件中告知Django認證系統使用咱們自定義的模型類。
在settings.py配置文件中進行設置
AUTH_USER_MODEL = 'users.User'
AUTH_USER_MODEL
參數的設置以點.
來分隔,表示應用名.模型類名
。
注意:Django建議咱們對於AUTH_USER_MODEL參數的設置必定要在第一次數據庫遷移以前就設置好,不然後續使用可能出現未知錯誤。
執行數據庫遷移
python manage.py makemigrations python manage.py migrate
在用戶註冊或登陸後,咱們想記錄用戶的登陸狀態,或者爲用戶建立身份認證的憑證。咱們再也不使用Session認證機制,而使用Json Web Token認證機制。
JWT就一段字符串,由三段信息構成的,將這三段信息文本用.
連接一塊兒就構成了Jwt字符串。就像這樣:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
第一部分咱們稱它爲頭部(header),第二部分咱們稱其爲載荷(payload, 相似於飛機上承載的物品),第三部分是簽證(signature).
jwt的頭部承載兩部分信息:
聲明類型,這裏是jwt
聲明加密的算法 一般直接使用 HMAC SHA256
完整的頭部就像下面這樣的JSON:
{ 'typ': 'JWT', 'alg': 'HS256' }
而後將頭部進行base64加密(該加密是能夠對稱解密的),構成了第一部分.
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
載荷就是存放有效信息的地方。這個名字像是特指飛機上承載的貨品,這些有效信息包含三個部分
標準中註冊的聲明
公共的聲明
私有的聲明
標準中註冊的聲明 (建議但不強制使用) :
iss: jwt簽發者
sub: jwt所面向的用戶
aud: 接收jwt的一方
exp: jwt的過時時間,這個過時時間必需要大於簽發時間
nbf: 定義在什麼時間以前,該jwt都是不可用的.
iat: jwt的簽發時間
jti: jwt的惟一身份標識,主要用來做爲一次性token,從而回避重放攻擊。
公共的聲明 : 公共的聲明能夠添加任何的信息,通常添加用戶的相關信息或其餘業務須要的必要信息.但不建議添加敏感信息,由於該部分在客戶端可解密.
私有的聲明 : 私有聲明是提供者和消費者所共同定義的聲明,通常不建議存放敏感信息,由於base64是對稱解密的,意味着該部分信息能夠歸類爲明文信息。
定義一個payload:
{ "sub": "1234567890", "name": "John Doe", "admin": true}
而後將其進行base64加密,獲得JWT的第二部分。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
JWT的第三部分是一個簽證信息,這個簽證信息由三部分組成:
header (base64後的)
payload (base64後的)
secret
這個部分須要base64加密後的header和base64加密後的payload使用.
鏈接組成的字符串,而後經過header中聲明的加密方式進行加鹽secret
組合加密,而後就構成了jwt的第三部分。
// javascript var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload); var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
關於簽發和核驗JWT,咱們可使用Django REST framework JWT擴展來完成。
文檔網站http://getblimp.github.io/django-rest-framework-jwt/
http://getblimp.github.io/django-rest-framework-jwt/
安裝
pip install djangorestframework-jwt
settings.py的配置代碼中
REST_FRAMEWORK = { # 用戶登錄認證方式 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.BasicAuthentication', ), } import datetime JWT_AUTH = { 'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1), }
JWT_EXPIRATION_DELTA 指明token的有效期
Django REST framework JWT 擴展的說明文檔中提供了手動簽發JWT的方法
from rest_framework_jwt.settings import api_settings jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER payload = jwt_payload_handler(user) token = jwt_encode_handler(payload)
在用戶註冊或登陸成功後,在序列化器中返回用戶信息之後同時返回token便可。
默認狀況下,djangorestframework-jwt這個模塊已經內置了一個登錄視圖接口給咱們了。咱們直接使用
from django.urls import path # jwt內部實現的登錄視圖 from rest_framework_jwt.views import obtain_jwt_token urlpatterns=[ path(r"login", obtain_jwt_token ), ]
在總路由文件中,urls.py中註冊子應用路由:
path('users/', include("users.urls")),
後端藉口基本寫完,如今咱們在前端編寫代碼接收接口數據:
咱們能夠將JWT保存在cookie中,也能夠保存在瀏覽器的本地存儲裏,咱們保存在瀏覽器本地存儲中
瀏覽器的本地存儲提供了sessionStorage 和 localStorage 兩種:
sessionStorage 會話存儲,瀏覽器關閉即失效
localStorage 永久存儲,長期有效
sessionStorage.變量名 = 變量值 // 保存數據 sessionStorage.變量名 // 讀取數據 sessionStorage.clear() // 清除全部sessionStorage保存的數據 localStorage.變量名 = 變量值 // 保存數據 localStorage.變量名 // 讀取數據 localStorage.clear() // 清除全部localStorage保存的數據
script發送axios以及回調函數處理的代碼:
methods:{ loginhander(){ this.$axios.post("http://127.0.0.1:8000/users/login",{ "username":this.username, "password":this.password },{ responseType:"json" }). then(response=>{ // 請求成功,保存登錄狀態 if(this.remember){ // 記住密碼 sessionStorage.removeItem("token"); let data = response.data; localStorage.token = data.token; }else{ // 不記住密碼 localStorage.removeItem("token"); let data = response.data; sessionStorage.token = data.token; } // 登陸成功之後,跳轉頁面 this.$router.go(-1); // this.$router.push("/home"); }).catch(error=>{ console.log(error); }) } }
<template> <div class="header"> <el-container> <el-header height="80px"> <el-row> <el-col class="logo" :span="3"> <router-link to="/"><img src="../../assets/head-logo.svg" alt=""></router-link> </el-col> <el-col :span="16"> <!-- gutter每一列之間的間隔空隙 --> <el-row class="nav" :gutter="20"> <el-col :span="3"><router-link :class="current_page==1?'active':''" to="/courses">免費課</router-link></el-col> <el-col :span="3"><router-link :class="current_page==2?'active':''" to="/courses">輕課</router-link></el-col> <el-col :span="3"><router-link :class="current_page==3?'active':''" to="/courses">學位課</router-link></el-col> <el-col :span="3"><router-link :class="current_page==4?'active':''" to="/courses">題庫</router-link></el-col> <el-col :span="3"><router-link :class="current_page==5?'active':''" to="/courses">教育</router-link></el-col> </el-row> </el-col> <el-col v-if="is_login" class="login-bar" :span="5"> <div class="cart-ico"> <b></b> <img src="../../assets/cart.svg" alt=""> <span>購物車</span> </div> <div class="study">學習中心</div> <div class="member"> <el-dropdown> <span class="el-dropdown-link"> <img src="../../assets/logo@2x.png" alt=""> </span> <el-dropdown-menu slot="dropdown"> <el-dropdown-item>個人帳戶 <i class="el-icon-arrow-right"></i></el-dropdown-item> <el-dropdown-item>個人帳戶 <i class="el-icon-arrow-right"></i></el-dropdown-item> <el-dropdown-item>個人帳戶 <i class="el-icon-arrow-right"></i></el-dropdown-item> <el-dropdown-item><span @click="logout">退出登陸</span> <i class="el-icon-arrow-right"></i></el-dropdown-item> </el-dropdown-menu> </el-dropdown> </div> </el-col> <el-col v-else class="login-bar" :span="5"> <div class="cart-ico"> <img src="../../assets/cart.svg" alt=""> <span>購物車</span> </div> <span class="header-login"><router-link to="/login">登陸</router-link></span> | <span class="header-register">註冊</span> </el-col> </el-row> </el-header> </el-container> </div> </template> <script> export default { name:"Header", props:["current_page"], data(){ return { token: localStorage.token || sessionStorage.token, is_login: false, /* 是否登陸 */ } }, created(){ // 登陸狀態判斷 if(this.token){ this.is_login=true; }else{ this.is_login=false; } }, methods:{ logout(){ localStorage.clear(); sessionStorage.clear(); this.is_login=false; alert("退出登陸成功"); } } } </script>
from rest_framework_jwt.views import obtain_jwt_token urlpatterns = [ path(r'login/$', obtain_jwt_token), ]
可是默認的返回值僅有token,咱們還需在返回值中增長username和user_id。
經過修改該視圖的返回值能夠完成咱們的需求。
在users/utils.py 中,建立
def jwt_response_payload_handler(token, user=None, request=None): """ 自定義jwt認證成功返回數據 :token 返回的jwt :user 當前登陸的用戶信息[對象] :request 當前本次客戶端提交過來的數據 """ return { 'token': token, 'id': user.id, 'username': user.username, }
修改settings.py配置文件
# JWT JWT_AUTH = { 'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1), 'JWT_RESPONSE_PAYLOAD_HANDLER': 'users.utils.jwt_response_payload_handler', }