drf的JWT認證

zong涵發表於2022-04-07

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擴充套件來完成。

文件網站:http://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

相關文章