Luffy之登陸認證以及JWT

1.用戶認證

在前面咱們已經完成了,前端登陸頁面的搭建,以及路由分配,如今咱們做關於登陸認證部分的東西javascript

 

Django提供了認證系統。認證系統包含:前端

  • 用戶vue

  • 權限:二元(是/否)標誌指示一個用戶是否能夠作一個特定的任務。java

  • 組:對多個用戶運用標籤和權限的一種通用的方式。python

  • 一個可配置的密碼哈希系統ios

  • 用戶登陸或內容顯示的表單和視圖git

  • 一個可插拔的後臺系統github

Django默認用戶的認證機制依賴Session機制,咱們在本項目中將引入JWT認證機制,將用戶的登錄狀態[認證信息]存放在Token字符串中,而後對接Django的認證系統,幫助咱們來實現:算法

  • 用戶的數據模型數據庫

  • 用戶密碼的加密與驗證

  • 用戶的權限系統

1.1 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 對象。當Noneraw_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_staffis_superuserTrue

2 建立自定義的用戶模型類

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

  

1.3 Django REST framework JWT

在用戶註冊或登陸後,咱們想記錄用戶的登陸狀態,或者爲用戶建立身份認證的憑證。咱們再也不使用Session認證機制,而使用Json Web Token認證機制。

1.3.1 JWT的構成

JWT就一段字符串,由三段信息構成的,將這三段信息文本用.連接一塊兒就構成了Jwt字符串。就像這樣:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

第一部分咱們稱它爲頭部(header),第二部分咱們稱其爲載荷(payload, 相似於飛機上承載的物品),第三部分是簽證(signature).

 

1.3.2 header

jwt的頭部承載兩部分信息:

  • 聲明類型,這裏是jwt

  • 聲明加密的算法 一般直接使用 HMAC SHA256

完整的頭部就像下面這樣的JSON:

{
  'typ': 'JWT',
  'alg': 'HS256'
}

  而後將頭部進行base64加密(該加密是能夠對稱解密的),構成了第一部分.

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

 

1.3.3 payload

載荷就是存放有效信息的地方。這個名字像是特指飛機上承載的貨品,這些有效信息包含三個部分

  • 標準中註冊的聲明

  • 公共的聲明

  • 私有的聲明

標準中註冊的聲明 (建議但不強制使用) :

  • 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

  

1.3.4 signature

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

  將這三部分用.鏈接成一個完整的字符串,構成了最終的jwt:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

  **注意:secret是保存在服務器端的,jwt的簽發生成也是在服務器端的,secret就是用來進行jwt的簽發和jwt的驗證,因此,它就是你服務端的私鑰,在任何場景都不該該流露出去。一旦客戶端得知這個secret, 那就意味着客戶端是能夠自我簽發jwt了。**

關於簽發和核驗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的有效期

 

1.3.6 使用

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這個模塊已經內置了一個登錄視圖接口給咱們了。咱們直接使用

當前users的urls.py文件代碼:

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

咱們能夠將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);
        })
      }
    }
View Code

前臺首頁判斷登陸狀態顯示對應信息

header.vue

<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>
             &nbsp;&nbsp;|&nbsp;&nbsp;
             <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>
View Code

 

jwt後端認證(自定義)

Django REST framework JWT提供了登陸簽發JWT的視圖,能夠直接使用

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',
}
相關文章
相關標籤/搜索