jwt原理
使用jwt認(rèn)證和使用session認(rèn)證的區(qū)別
文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-463721.html
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-463721.html
三段式
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
# 1. header jwt的頭部承載兩部分信息:
聲明類(lèi)型 這里是jwt
聲明加密的算法 通常直接使用 HMSC SHA256
公司信息
{
'typ':'jwt',
'alg':'HS256'
}
然后將頭部進(jìn)行base64編碼 構(gòu)成了第一部分
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
# 2. payload 載荷就是存放有效信息的地方
當(dāng)前用戶信息 用戶名 用戶id
exp:jwt的過(guò)期時(shí)間 這個(gè)過(guò)期時(shí)間必須要大于簽發(fā)時(shí)間
定義一個(gè)payload:
{
"exp":"1234567890",
"name":"jason",
"user_id":99
}
然后將其進(jìn)行base64編碼 得到j(luò)wt的第二部分
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
# signature JWT的第三部分是一個(gè)簽證信息,這個(gè)簽證信息由三部分組成:
header (base64后的)
payload (base64后的)
secret
這個(gè)部分需要base64加密后的header和base64加密后的payload使用.連接組成的字符串,然后通過(guò)header中聲明的加密方式進(jìn)行加鹽secret組合加密,然后就構(gòu)成了jwt的第三部分。
將這三部分用.連接成一個(gè)完整的字符串,構(gòu)成了最終的jwt:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
jwt開(kāi)發(fā)流程
# 使用jwt認(rèn)證的開(kāi)發(fā)流程 就是兩部分
第一部分:簽發(fā)token的過(guò)程 登錄做的
用戶攜帶用戶名和密碼 訪問(wèn)我 我們校驗(yàn)通過(guò),生成token串,返回給前端
第二部分:token認(rèn)證過(guò)程 登錄認(rèn)證時(shí)使用 其實(shí)就是咱們之前講的認(rèn)證類(lèi) 在認(rèn)證類(lèi)中完成對(duì)token的認(rèn)證操作
用戶訪問(wèn)我們需要登陸后才能訪問(wèn)的接口,必須攜帶我們簽發(fā)的token串(請(qǐng)求頭)
我們?nèi)〕鰐oken 驗(yàn)證該token是否過(guò)期,是否被篡改,是否是偽造的
如果正常,說(shuō)明荷載中的數(shù)據(jù),就是安全的,可以根據(jù)荷載中的用戶id,查詢出當(dāng)前登錄用戶,放到request中即可
drf-jwt快速使用
# djagno+drf框架中,使用jwt來(lái)做登錄認(rèn)證
# 使用第三方:
-django-rest-framework-jwt:https://github.com/jpadilla/django-rest-framework-jwt
-djangorestframework-simplejwt:https://github.com/jazzband/djangorestframework-simplejwt
# 我們可以自己封裝 :https://gitee.com/liuqingzheng/rbac_manager/tree/master/libs/lqz_jwt
# 安裝
pip3 install djangorestframework-jwt
# 補(bǔ)充:
密碼明文一樣,每次加密后都不一樣,如何做,動(dòng)態(tài)加鹽,動(dòng)態(tài)的鹽要保存,跟密碼保存在一起
有代碼 有數(shù)據(jù)庫(kù) 沒(méi)有登不進(jìn)去的系統(tǒng)
# 解決不了:
token被獲取,模擬發(fā)送請(qǐng)求
不能篡改
不能偽造
# 快速使用
簽發(fā)過(guò)程(快速簽發(fā)),必須是auth的user表(人家?guī)湍銓?xiě)好了)
登錄接口--》基于auth的user表簽發(fā)的
認(rèn)證過(guò)程
登錄認(rèn)證,認(rèn)證類(lèi)(寫(xiě)好了)
# 總結(jié):
簽發(fā):只需要在路由中配置
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
path('login/', obtain_jwt_token),
]
認(rèn)證:視圖類(lèi)上加
class BookView(ViewSet):
authentication_classes = [JSONWebTokenAuthentication] # 認(rèn)證類(lèi),drf-jwt提供的
permission_classer = [IsAuthenticated] # 權(quán)限類(lèi) drf提供的
訪問(wèn)的時(shí)候 要在請(qǐng)求頭中攜帶 必須叫
Authorization:jwt token串
drf-jwt定制返回格式
# 登錄簽發(fā)token的接口 要返回code,msg,username,token等信息
參照:jwt源碼
from rest_framework_jwt import settings # 配置文件
from rest_framework_jwt.utils import jwt_response_payload_handler # 返回格式
# 對(duì)返回格式進(jìn)行定制
1 寫(xiě)個(gè)函數(shù) 函數(shù)返回字典格式 返回的格式 會(huì)被序列化 前端看到
def common_response(token, user=None, request=None):
return {
'code':100,
'msg':'登錄成功',
'token':token,
'username':user.username
}
2 寫(xiě)的函數(shù)在配置文件中配置
JWT_AUTH = {
'JWT_RESPONSE_PAYLOAD_HANDLER':'app01.jwt_response.common_response',
}
drf-jwt自定義用戶表簽發(fā)
# 1 創(chuàng)建一個(gè)用戶表
class User(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=64)
age = models.IntegerField()
email = models.CharField(max_length=64)
# 2 登錄接口
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
class UserView(ViewSet):
def login(self,request):
username = request.data.get('username')
password = request.data.get('password')
user = User.objects.filter(username=username,password=possword).first()
if user:
# 登錄成功,簽發(fā)token-->先背過(guò),
# 1 通過(guò)user 獲取payload
payload = jwt_payload_handler(user)
print(payload)
# 2 通過(guò)payload 得到token
token = jwt_encode_handler(payload)
return Response({'code': 100, 'msg': '登錄成功', 'username': user.username, 'token': token})
else:
return Response({'code': 101, 'msg': '用戶名或密碼錯(cuò)誤'})
# 3 登錄接口簽發(fā)token返回給前端
drf-jwt自定義認(rèn)證類(lèi)
# drf的認(rèn)證類(lèi)定義方式,之前學(xué)過(guò)
# 在認(rèn)證類(lèi)中,自己寫(xiě)邏輯
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
import jwt
from rest_framework_jwt.settings import api_settings
from .models import User
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
# 根據(jù)JSONWebTokenAuthentication重寫(xiě)authenticate方法
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
class JWTAuthentication(BaseAuthentication):
def authenticate(self,request):
token = request.META.get('HTTP_TOKEN')
# 背過(guò)
# 校驗(yàn)token是否過(guò)期 合法 如果都通過(guò) 查詢出當(dāng)前用戶 返回
# 如果不通過(guò) 拋異常
try:
payload = jwt_decode_handler(token)
# 如果認(rèn)證通過(guò) payload就可以認(rèn)為是安全的 我們就可以使用
user_id = payload.get('user_id')
# 每個(gè)需要登錄后 才能訪問(wèn)的接口 都會(huì)走這個(gè)認(rèn)證類(lèi) 一旦走到這個(gè)認(rèn)證類(lèi),就會(huì)去數(shù)據(jù)庫(kù)查詢一次數(shù)據(jù)
user = User.objects.get(pk=user_id)
# 優(yōu)化后的
# user = User(username=payload.get('username'),id=user_id)
# user = {'username':payload.get('username'),'id':user_id}
except jwt.ExpiredSignature:
raise AuthenticationFailed('token過(guò)期')
except jwt.DecodeError:
raise AuthenticationFailed('解碼失敗')
except jwt.InvalidTokenError:
raise AuthenticationFailed('token認(rèn)證異常')
except Exception:
raise AuthenticationFailed('token認(rèn)證異常')
return user,token
# 局部使用和全局使用
drf-jwt的簽發(fā)源碼分析
# from rest_framework_jwt.views import obtain_jwt_token
# obtain_jwt_token就是ObtainJSONWebToken.as_view()---》視圖類(lèi).as_view()
# 視圖類(lèi)
class ObtainJSONWebToken(JSONWebTokenAPIView):
serializer_class = JSONWebTokenSerializer
# 父類(lèi):JSONWebTokenAPIView
class JSONWebTokenAPIView(APIView):
# 局部禁用掉權(quán)限和認(rèn)證
permission_classes = ()
authentication_classes = ()
def post(self, request, *args, **kwargs):
# serializer=JSONWebTokenSerializer(data=request.data)
serializer = self.get_serializer(data=request.data)
# 調(diào)用序列化列的is_valid---》字段自己的校驗(yàn)規(guī)則,局部鉤子和全局鉤子
# 讀JSONWebTokenSerializer的局部或全局鉤子
if serializer.is_valid(): # 全局鉤子在校驗(yàn)用戶,生成token
# 從序列化類(lèi)中取出user
user = serializer.object.get('user') or request.user
# 從序列化類(lèi)中取出token
token = serializer.object.get('token')
# 咱么定制返回格式的時(shí)候,寫(xiě)的就是這個(gè)函數(shù)
response_data = jwt_response_payload_handler(token, user, request)
response = Response(response_data)
return response
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# JSONWebTokenSerializer的全局鉤子
class JSONWebTokenSerializer(Serializer):
def validate(self, attrs):
# attrs前端傳入的,校驗(yàn)過(guò)后的數(shù)據(jù) {username:lqz,password:lqz12345}
credentials = {
'username':attrs.get('username'),
'password': attrs.get('password')
}
if all(credentials.values()): # 校驗(yàn)credentials中字典的value值是否都不為空
# user=authenticate(username=前端傳入的,password=前端傳入的)
# auth模塊的用戶名密碼認(rèn)證函數(shù),可以傳入用戶名密碼,去auth的user表中校驗(yàn)用戶是否存在
# 等同于:User.object.filter(username=username,password=加密后的密碼).first()
user = authenticate(**credentials)
if user:
if not user.is_active:
msg = _('User account is disabled.')
raise serializers.ValidationError(msg)
payload = jwt_payload_handler(user)
return {
'token': jwt_encode_handler(payload),
'user': user
}
else:
msg = _('Unable to log in with provided credentials.')
raise serializers.ValidationError(msg)
else:
msg = _('Must include "{username_field}" and "password".')
msg = msg.format(username_field=self.username_field)
raise serializers.ValidationError(msg)
drf-jwt的認(rèn)證源碼分析
# from rest_framework_jwt.authentication import JSONWebTokenAuthentication
# JSONWebTokenAuthentication
class JSONWebTokenAuthentication(BaseJSONWebTokenAuthentication):
def get_jwt_value(self, request):
# request-->jwt 'token串'
auth = get_authorization_header(request).split()
# 按照空格切割之后-->auth=['jwt','token串']
auth_header_prefix = api_settings.JWT_AUTH_HEADER_PREFIX.lower()
if not auth:
if api_settings.JWT_AUTH_COOKIE:
return request.COOKIES.get(api_settings.JWT_AUTH_COOKIE)
return None
if smart_text(auth[0].lower()) != auth_header_prefix:
return None
if len(auth) == 1:
msg = _('Invalid Authorization header. No credentials provided.')
raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
msg = _('Invalid Authorization header. Credentials string '
'should not contain spaces.')
raise exceptions.AuthenticationFailed(msg)
return auth[1] # auth取索引1 ---> token串
# 因?yàn)槲覀冎貙?xiě)了authenticate
# JSONWebTokenAuthentication中沒(méi)有要從父類(lèi)中查找
# 父類(lèi)中找:BaseJSONWebTokenAuthentication---》authenticate,找到了
def authenticate(self, request):
# 調(diào)JSONWebTokenAuthentication中的get_jwt_value
# 拿到前端傳入的token串
jwt_value = self.get_jwt_value(request)
# 如果前端沒(méi)傳 返回None,request.user中就沒(méi)有當(dāng)前登錄用戶
# 如果前端沒(méi)有攜帶token,也能進(jìn)入到視圖類(lèi)的方法中執(zhí)行,控制不住登錄用戶
# 所以咱們才加了個(gè)權(quán)限類(lèi),來(lái)做控制
if jwt_value is None:
return None
try:
payload = jwt_decode_handler(jwt_value)
except jwt.ExpiredSignature:
msg = _('Signature has expired.')
raise exceptions.AuthenticationFailed(msg)
except jwt.DecodeError:
msg = _('Error decoding signature.')
raise exceptions.AuthenticationFailed(msg)
except jwt.InvalidTokenError:
raise exceptions.AuthenticationFailed()
# 調(diào)用authenticate_credentials通過(guò)payload會(huì)返回當(dāng)前登錄用戶
user = self.authenticate_credentials(payload)
return (user, jwt_value)
# 如果用戶不攜帶token,也能認(rèn)證通過(guò)
# 所以我們必須加個(gè)權(quán)限類(lèi)來(lái)限制
from rest_framework.permissions import IsAuthenticated # 是否認(rèn)證通過(guò)
class IsAuthenticated(BasePermission):
def has_permission(self, request, view):
# 必須有當(dāng)前登錄用戶且已經(jīng)完成認(rèn)證
return bool(request.user and request.user.is_authenticated)
到了這里,關(guān)于drf——jwt的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!