drf☞jwt自動簽發與手動簽發
阿新 • • 發佈:2020-07-15
[TOC]
## 一、自動簽發
**urls**
```python
from rest_framework_jwt.views import obtain_jwt_token
# 使用jwt自帶的登入檢視
urlpatterns = [
path('login/', obtain_jwt_token),
]
```
**settings**
```python
import datetime
JWT_AUTH={
# 配置響應格式,必須和自動簽發使用
'JWT_RESPONSE_PAYLOAD_HANDLER':'app01.utils.my_jwt_response_payload_handler',
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7), # 過期時間,手動配置
}
```
**utils**
```python
# 重寫jwt響應格式(需要到settings配置)
# 與之配合使用的必須是自動簽發
def my_jwt_response_payload_handler(token, user=None, request=None): # 返回什麼,前端就能看到什麼樣子
return {
'token': token,
'msg':'登入成功',
'status':100,
'username':user.username
}
```
然後直接在前端提交post請求傳送賬號和密碼,會返回我們定義好的響應格式
```python
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6Imh6IiwiZXhwIjoxNTk1NDE1MTEyLCJlbWFpbCI6IiJ9.BxBdsm6oBz8iPAwSSpo_7IaU4pBp6RjK4c0GJ_FYN1E",
"msg": "登入成功",
"status": 100,
"username": "hz"
}
```
然後拿出token對測試類傳送測試請求
```python
class TestAPI(APIView):
def get(self,request):
print(request.user)
return Response("ok")
# 因為內建的他沒有對匿名使用者設定攔截,素以匿名使用者也能看到ok
# 我們用request.user來區分
# 這裡可能會出現我登入了很多次,用每次不同的token都能登入
# 這是因為token校驗的是規則,是要加密規則符合且沒有超時,那用哪次token都一樣的
```
## 二、手動簽發
**utils**
```python
from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication,jwt_decode_handler
import jwt
from rest_framework.exceptions import AuthenticationFailed
class MyAuthentication(BaseJSONWebTokenAuthentication):
# 這裡重寫的邏輯和BaseJSONWebTokenAuthentication裡的authenticate一模一樣
def authenticate(self, request):
jwt_token = request.META.get('HTTP_AUTHORIZATION') # 獲取瀏覽器傳來的token
if jwt_token:
try:
payload = jwt_decode_handler(jwt_token) # 傳入token,拿出第二段使用者資訊,有內建的校驗token功能
except jwt.ExpiredSignature:
raise AuthenticationFailed('簽名過期')
except jwt.InvalidTokenError:
raise AuthenticationFailed('使用者非法')
except Exception as e:
# 所有異常都會走到這
raise AuthenticationFailed(str(e))
# 通過內建的方法把payload轉換成使用者物件
user = self.authenticate_credentials(payload)
return user,None # ===》request.user,request.auth
raise AuthenticationFailed('您沒有攜帶認證資訊')
```
**sers**
```python
from rest_framework import serializers
# 多方序列化校驗登入
import re
from rest_framework.exceptions import ValidationError
from app01 import models
from rest_framework_jwt.utils import jwt_encode_handler,jwt_payload_handler
class LoginSer(serializers.ModelSerializer):
# 我們要提交校驗資料的時候,如果直接用下面Meta繫結給模型類的話
# 關鍵點2:這裡如果不寫username的話,序列化器直接用的是模型類的username
# 這兩者的區別在於,如果覆蓋寫了username,他表示的可以是任何前端傳來的資料,如果是模型類繫結,那隻能是使用者名稱了
# 我們這裡username用於多方登入的校驗資料,必須要重寫
# 而password不用重寫,因為password用的就是模型類本身的
username = serializers.CharField()
class Meta:
model = models.User
fields =['username','password']
def validate(self, attrs):
username = attrs.get('username')
password = attrs.get('password')
if username:
if re.match('^1[3-9][0-9]{9}$', username):
user = models.User.objects.filter(mobile=username).first()
elif re.match('^.+@.+$', username): # 郵箱
user = models.User.objects.filter(email=username).first()
else:
user = models.User.objects.filter(username=username).first()
if user:
if user.check_password(password):
# 關鍵點3:jwt_payload_handler把使用者資料物件轉化成使用者資訊的字典
# jwt_encode_handler把使用者資訊的字典轉化成token
payload = jwt_payload_handler(user)
# print('user:',user,type(user))
token = jwt_encode_handler(payload)
# print('payload:',payload,type(payload))
# print('token:',token)
# 關鍵點4:如果我們要給序列化器新增資料,讓檢視函式去使用
# 通常都是傳給物件的context屬性,當然直接賦值也可以,這只是他給我們提供的傳值介面
self.context['token'] = token
self.context['user'] = user
self.user = user
return attrs
else:
raise ValidationError('密碼錯誤')
raise ValidationError('不存在使用者')
raise ValidationError('請輸入使用者名稱')
```
**views**
```python
class LoginApi(ViewSet):
authentication_classes = []
def login(self, request):
# 在呼叫序列化類給context傳資料,可以直接在序列化類中呼叫
# 關鍵點1:注意區分序列化傳值與反序列化
# 這裡只要拿字典取校驗資料,那就傳給data
# 如果是要把資料物件轉化成字典就傳給instance
user_ser = sers.LoginSer(data=request.data, context={'request': request})
user_ser.is_valid(raise_exception=True)
token = user_ser.context.get('token')
user = user_ser.context.get('user')
print(user_ser.user)
return Response({'code': 100, 'msg': '登入成功', 'token': token, 'username': user.usernam