很是詳細的Django使用Token(轉)

基於Token的身份驗證

在實現登陸功能的時候,正常的B/S應用都會使用cookie+session的方式來作身份驗證,後臺直接向cookie中寫數據,可是因爲移動端的存在,移動端是沒有cookie機制的,因此使用token能夠實現移動端和客戶端的token通訊。redis

驗證流程

整個基於Token的驗證流程以下:算法

  1. 客戶端使用用戶名跟密碼請求登陸
  2. 服務器收到請求,去驗證用戶名和密碼
  3. 驗證成功後,服務端會簽發一個Token,再把這個Token發送到客戶端
  4. 客戶端收到的Token之後能夠把它存儲起來,好比放在Cookie或LocalStorage裏
  5. 客戶端每次向服務器發送其餘請求的時候都要帶着服務器簽發的Token
  6. 服務器收到請求,去驗證客戶端請求裏面帶着的Token,若是驗證成功,就像客戶端返回請求的數據

JWT

構造Token的方法挺多的,能夠說只要是客戶端和服務器端約定好了格式,是想怎麼寫就怎麼寫的,然而還有一些標準寫法,例如JWT讀做/jot/,表示:JSON Web Tokens.
JWT標準的Token有三個部分:數據庫

  • header
  • payload
  • signature
    三個部分會用點分割開,而且都會使用Base64編碼,因此真正的Token看起來像這樣
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ.SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc

Header

header部分主要是兩部份內容,一個是Token的類型,另外一個是使用的算法,好比下面的類型就是JWT,使用的算法是HS256:django

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

上面的內容要用 Base64 的形式編碼一下,因此就變成這樣:緩存

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

Payload

Payload 裏面是 Token 的具體內容,這些內容裏面有一些是標準字段,你也能夠添加其它須要的內容。下面是標準字段:服務器

  • iss:Issuer,發行者
  • sub:Subject,主題
  • aud:Audience,觀衆
  • exp:Expiration time,過時時間
  • nbf:Not before
  • iat:Issued at,發行時間
  • jti:JWT ID

Signature

JWT的最後一部分是Signature,這部分至關於前兩段的摘要,用來防止其餘人來篡改Token中的信息,在處理時能夠首先將前兩段生成的內容使用Base64生成一下再加鹽而後利用MD5等摘要算法在生成一遍cookie

服務端生成Token

在服務端生成Token的時候,須要解決兩個問題session

  1. 使用什麼加密算法
  2. Token如何存儲

加密算法

這裏的加密算法並非MD5,SHA1這樣的哈希算法,由於這種算法是沒法解密的,只能用來生成摘要,在Django中內置了一個加密前面模塊django.core.signing模塊,能夠用來加密和解密任何數據,使用簽名模塊的dumps和load函數來實現分佈式

示例:函數

from django.core import signing
value = signing.dumps({"foo":"bar"})
src = signing.loads(value)
print(value)
print(src)

結果:

eyJmb28iOiJiYXIifQ:1NMg1b:zGcDE4-TCkaeGzLeW9UQwZesciI 
{‘foo’: ‘bar’}

Token如何存儲

用什麼存儲

在服務器中Token能夠存儲在內存中,由於本質是字符串,因此並不會佔用很大的內存空間,若是是分佈式的存儲能夠將全部的token信息分段存儲在不一樣的服務器中,也能夠存儲在數據庫中,在Django中提供了緩存類,能夠用來存儲Token,Django的緩存能夠結合Redis來使用,能夠藉助django-redis來實現

安裝

pip install django-redis

配置

爲了使用django-redis,須要將django cache setting修改,修改settings.py,默認是沒有Cache的配置信息的,在其中添加:

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    }
}

固然,須要你首先安裝了redis

cache使用

cache使用的時候基本可使用set和get方法來進行存/取數據

>>> cache.set('my_key', 'hello, world!', 30)
>>> cache.get('my_key')
'hello, world!'

如何存

因爲redis是使用k-v模式來進行存儲數據的,咱們可使用用戶名做爲key,而token信息做爲value,相較於直接使用token做爲key的方式,好處是咱們可使用更少的空間實現一些功能,例如當用戶修改了密碼或點擊註銷以後,它的token能夠直接失效,直接將該用戶名所對應的數據刪除就行了,或者用戶在一次登陸成功後,又一次請求了登陸接口,咱們能夠很簡單的更新該用戶的token信息,而這樣存儲所依賴於咱們的token能夠進行解密,若是你是直接生成了一串沒法解密的數據做爲token,不能使用用戶名做爲token了

code

import time
from django.core import signing
import hashlib
from django.core.cache import cache

HEADER = {'typ': 'JWP', 'alg': 'default'}
KEY = 'CHEN_FENG_YAO'
SALT = 'www.lanou3g.com'
TIME_OUT = 30 * 60  # 30min


def encrypt(obj):
    """加密"""
    value = signing.dumps(obj, key=KEY, salt=SALT)
    value = signing.b64_encode(value.encode()).decode()
    return value


def decrypt(src):
    """解密"""
    src = signing.b64_decode(src.encode()).decode()
    raw = signing.loads(src, key=KEY, salt=SALT)
    print(type(raw))
    return raw


def create_token(username):
    """生成token信息"""
    # 1. 加密頭信息
    header = encrypt(HEADER)
    # 2. 構造Payload
    payload = {"username": username, "iat": time.time()}
    payload = encrypt(payload)
    # 3. 生成簽名
    md5 = hashlib.md5()
    md5.update(("%s.%s" % (header, payload)).encode())
    signature = md5.hexdigest()
    token = "%s.%s.%s" % (header, payload, signature)
    # 存儲到緩存中
    cache.set(username, token, TIME_OUT)
    return token


def get_payload(token):
    payload = str(token).split('.')[1]
    payload = decrypt(payload)
    return payload


# 經過token獲取用戶名
def get_username(token):
    payload = get_payload(token)
    return payload['username']
    pass


def check_token(token):
    username = get_username(token)
    last_token = cache.get(username)
    if last_token:
        return last_token == token
    return False
相關文章
相關標籤/搜索