drf的JWT認證

JWT認證(5星)

token發展史

在用戶註冊或登錄後,我們想記錄用戶的登錄狀態,或者為用戶創建身份認證的憑證。我們不再使用Session認證機制,而使用Json Web Token(本質就是token)認證機制。

image

image

image

image

構成和工作原理

JWT的構成

JWT就是一段字元串,由三段資訊構成的,將這三段資訊文本用.鏈接一起就構成了Jwt字元串。就像這樣:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

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

header(頭部)

jwt的頭部承載兩部分資訊:

  • 聲明類型,這裡是jwt
  • 聲明加密的演算法 通常直接使用 HMAC SHA256

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

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

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

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

payload(荷載)

載荷就是存放類似用戶資訊,過期時間,簽發時間…

{
    "userid": "1",
    "name": "John Doe",
    "exp": 1214356
}

然後將其進行base64加密,得到JWT的第二部分。

eyJ1c2VyaWQiOiAiMSIsICJuYW1lIjogIkpvaG4gRG9lIiwgImV4cCI6IDEyMTQzNTZ9

signature(簽證)

JWT的第三部分是一個簽證資訊,這個簽證資訊由三部分組成:

  • header (base64解密後加密演算法加密後的)
  • payload (base64解密後加密演算法加密後的)
  • secret(密鑰=加鹽)

這個部分需要base64加密後的header和base64加密後的payload使用.連接組成的字元串,然後通過header中聲明的加密方式進行加鹽secret組合加密,然後就構成了jwt的第三部分。

TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

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

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyaWQiOiAiMSIsICJuYW1lIjogIkpvaG4gRG9lIiwgImV4cCI6IDEyMTQzNTZ9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

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

關於簽發和核驗JWT,我們可以使用Django REST framework JWT擴展來完成。

文檔網站://getblimp.github.io/django-rest-framework-jwt/

補充base64編碼解碼

import base64
import json

payload = {
    "userid": "1",
    "name": "John Doe",
    "exp": 1214356
}
json_payload = json.dumps(payload)
# 編碼
res = base64.b64encode(json_payload.encode('utf8'))

print(res)
# 解碼
res2 = json.loads(base64.b64decode(res))
print(res2)

# b'eyJ1c2VyaWQiOiAiMSIsICJuYW1lIjogIkpvaG4gRG9lIiwgImV4cCI6IDEyMTQzNTZ9'
# {'userid': '1', 'name': 'John Doe', 'exp': 1214356}

本質原理

jwt認證演算法:簽發與校驗

1)jwt分三段式:頭.體.簽名 (head.payload.sgin)
2)頭和體是可逆加密,讓伺服器可以反解出user對象;簽名是不可逆加密,保證整個token的安全性的(base64反解出的是hash加密後的密文)
3)頭體簽名三部分,都是採用json格式的字元串,進行加密,可逆加密一般採用base64演算法,不可逆加密一般採用hash(md5)演算法
4)頭中的內容是基本資訊:公司資訊、項目組資訊、token採用的加密方式資訊
{
	"company": "公司資訊",
	...
}
5)體中的內容是關鍵資訊:用戶主鍵、用戶名、簽發時客戶端資訊(設備號、地址)、過期時間
{
	"user_id": 1,
	...
}
6)簽名中的內容是安全資訊:頭的加密結果 + 體的加密結果 + 伺服器不對外公開的安全碼(對整個字典進行md5加密)
{
	"head": "頭的加密字元串",
	"payload": "體的加密字元串",
	"secret": "安全碼"
}

簽發:根據登錄請求提交來的 帳號 + 密碼 + 設備資訊 簽發 token

1)用基本資訊存儲json字典,採用base64演算法加密得到 頭字元串
2)用關鍵資訊存儲json字典,採用base64演算法加密得到 體字元串
3)用頭、體加密字元串再加安全碼資訊存儲json字典,採用hash md5演算法加密得到 簽名字元串

帳號密碼就能根據User表得到user對象,形成的三段字元串用 . 拼接成token返回給前台

校驗:根據客戶端帶token的請求 反解出 user 對象

1)將token按 . 拆分為三段字元串,第一段 頭加密字元串 一般不需要做任何處理
2)第二段 體加密字元串,要反解出用戶主鍵,通過主鍵從User表中就能得到登錄用戶,過期時間和設備資訊都是安全資訊,確保token沒過期,且時同一設備來的
3)再用 第一段 + 第二段 + 伺服器安全碼 不可逆md5加密,與第三段 簽名字元串 進行碰撞校驗,通過後才能代表第二段校驗得到的user對象就是合法的登錄用戶

drf項目的jwt認證開發流程(重點)

1)用帳號密碼訪問登錄介面,登錄介面邏輯中調用 簽發token 演算法,得到token,返回給客戶端,客戶端自己存到cookies中

2)校驗token的演算法應該寫在認證類中(在認證類中調用),全局配置給認證組件,所有視圖類請求,都會進行認證校驗,所以請求帶了token,就會反解出user對象,在視圖類中用request.user就能訪問登錄的用戶

註:登錄介面需要做 認證 + 許可權 兩個局部禁用

drf-jwt安裝和簡單使用(2星)

安裝

pip3 install djangorestframework-jwt

簡單使用

簽發

# 1 創建超級用戶
python3 manage.py createsuperuser
# 解釋下為什麼要創建超級用戶:因為djangorestframework-jwt認證是基於django的auth里的user表作關聯的,所以驗證的數據也必須源自於這張表
# 2 配置路由urls.py
from django.urls import path
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
    path('login/', obtain_jwt_token),
]
# 3 postman測試
向後端介面發送post請求,攜帶用戶名密碼,即可看到生成的token

image

認證

from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.permissions import IsAuthenticated

class BookAPIView(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookModelSerializer
    # 必須用這個認證類
    authentication_classes = [JSONWebTokenAuthentication, ]
    # 還要配合這個許可權
    permission_classes = [IsAuthenticated, ]

在postman里

image

image

JWT使用auth表簽發token,自訂製格式(3星)

配置setting.py

JWT_AUTH ={
    # token的過期時間
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),
    # 如果不自定義,返回的格式是固定的,只有token欄位
    # 這裡把下面自訂製的函數註冊進來
    'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.utils.jwt_response_payload_handler',
}

自訂製的py文件內

def jwt_response_payload_handler(token, user=None, request=None):
    return {
        'code': 1000,
        'msg': '登陸成功',
        'username': user.username,
        'token': token
    }

這時登陸時返回的格式就變成了:

image

djangorestframework-jwt模組源碼分析(2星)

簽發token

ObtainJSONWebToken.as_view()--->ObtainJSONWebToken---->post方法
 def post(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid():  # 驗證用戶登錄和簽發token,都在序列化類的validate方法中完成的
            user = serializer.object.get('user') or request.user
            token = serializer.object.get('token')
            response_data = jwt_response_payload_handler(token, user, request)
            response = Response(response_data)
            # 返回了咱們自定指的格式 
            '''
               {
                'code':100,
                'msg':'登錄成功',
                'username':user.username,
                'token': token,
            }
            
            '''
            return response
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
    
    # 全局鉤子函數
     def validate(self, attrs):
        credentials = {
            self.username_field: attrs.get(self.username_field),
            'password': attrs.get('password')
        }

        if all(credentials.values()):
            # 根據用戶名密碼去auth的user表校驗,是否存在
            user = authenticate(**credentials)

            if user:
                if not user.is_active:
                    msg = _('User account is disabled.')
                    raise serializers.ValidationError(msg)
				# 生成payload
                payload = jwt_payload_handler(user)

                return {
                    'token': jwt_encode_handler(payload), # 通過payload生成token
                    'user': user
                }
            else:
               # 不在拋異常,前端就看到資訊了
                raise serializers.ValidationError(msg)
        else:
            raise serializers.ValidationError(msg)

image

image

image

image

image

image

image

認證

image

image

image

image

image