Django+JWT实现Token认证
對(duì)外提供API不用django rest framework(DRF)就是旁門左道嗎?
基于Token的鑒權(quán)機(jī)制越來(lái)越多的用在了項(xiàng)目中,尤其是對(duì)于純后端只對(duì)外提供API沒(méi)有web頁(yè)面的項(xiàng)目,例如我們通常所講的前后端分離架構(gòu)中的純后端服務(wù),只提供API給前端,前端通過(guò)API提供的數(shù)據(jù)對(duì)頁(yè)面進(jìn)行渲染展示或增加修改等,我們知道HTTP是一種無(wú)狀態(tài)的協(xié)議,也就是說(shuō)后端服務(wù)并不知道是誰(shuí)發(fā)來(lái)的請(qǐng)求,那么如何校驗(yàn)請(qǐng)求的合法性呢?這就需要通過(guò)一些方式對(duì)請(qǐng)求進(jìn)行鑒權(quán)了
先來(lái)看看傳統(tǒng)的登錄鑒權(quán)跟基于Token的鑒權(quán)有什么區(qū)別
以Django的賬號(hào)密碼登錄為例來(lái)說(shuō)明傳統(tǒng)的驗(yàn)證鑒權(quán)方式是怎么工作的,當(dāng)我們登錄頁(yè)面輸入賬號(hào)密碼提交表單后,會(huì)發(fā)送請(qǐng)求給服務(wù)器,服務(wù)器對(duì)發(fā)送過(guò)來(lái)的賬號(hào)密碼進(jìn)行驗(yàn)證鑒權(quán),驗(yàn)證鑒權(quán)通過(guò)后,把用戶信息記錄在服務(wù)器端(django_session表中),同時(shí)返回給瀏覽器一個(gè)sessionid用來(lái)唯一標(biāo)識(shí)這個(gè)用戶,瀏覽器將sessionid保存在cookie中,之后瀏覽器的每次請(qǐng)求都一并將sessionid發(fā)送給服務(wù)器,服務(wù)器根據(jù)sessionid與記錄的信息做對(duì)比以驗(yàn)證身份
Token的鑒權(quán)方式就清晰很多了,客戶端用自己的賬號(hào)密碼進(jìn)行登錄,服務(wù)端驗(yàn)證鑒權(quán),驗(yàn)證鑒權(quán)通過(guò)生成Token返回給客戶端,之后客戶端每次請(qǐng)求都將Token放在header里一并發(fā)送,服務(wù)端收到請(qǐng)求時(shí)校驗(yàn)Token以確定訪問(wèn)者身份
session的主要目的是給無(wú)狀態(tài)的HTTP協(xié)議添加狀態(tài)保持,通常在瀏覽器作為客戶端的情況下比較通用。而Token的主要目的是為了鑒權(quán),同時(shí)又不需要考慮CSRF防護(hù)以及跨域的問(wèn)題,所以更多的用在專門給第三方提供API的情況下,客戶端請(qǐng)求無(wú)論是瀏覽器發(fā)起還是其他的程序發(fā)起都能很好的支持。所以目前基于Token的鑒權(quán)機(jī)制幾乎已經(jīng)成了前后端分離架構(gòu)或者對(duì)外提供API訪問(wèn)的鑒權(quán)標(biāo)準(zhǔn),得到廣泛使用
JSON Web Token(JWT)是目前Token鑒權(quán)機(jī)制下最流行的方案,網(wǎng)上關(guān)于JWT的介紹有很多,這里不細(xì)說(shuō),只講下Django如何利用JWT實(shí)現(xiàn)對(duì)API的認(rèn)證鑒權(quán),搜了幾乎所有的文章都是說(shuō)JWT如何結(jié)合DRF使用的,如果你的項(xiàng)目沒(méi)有用到DRF框架,也不想僅僅為了鑒權(quán)API就引入龐大復(fù)雜的DRF框架,那么可以接著往下看
我的需求如下:
PyJWT介紹
要實(shí)現(xiàn)上邊的需求1,我們首先得引入JWT模塊,python下有現(xiàn)成的PyJWT模塊可以直接用,先看下JWT的簡(jiǎn)單用法
安裝PyJWT
$ pip install pyjwt利用PyJWT生成Token
>>> import jwt >>> encoded_jwt = jwt.encode({'username':'運(yùn)維咖啡吧','site':'https://ops-coffee.cn'},'secret_key',algorithm='HS256')這里傳了三部分內(nèi)容給JWT,
第一部分是一個(gè)Json對(duì)象,稱為Payload,主要用來(lái)存放有效的信息,例如用戶名,過(guò)期時(shí)間等等所有你想要傳遞的信息
第二部分是一個(gè)秘鑰字串,這個(gè)秘鑰主要用在下文Signature簽名中,服務(wù)端用來(lái)校驗(yàn)Token合法性,這個(gè)秘鑰只有服務(wù)端知道,不能泄露
第三部分指定了Signature簽名的算法
查看生成的Token
>>> print(encoded_jwt) b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6Ilx1OGZkMFx1N2VmNFx1NTQ5Nlx1NTU2MVx1NTQyNyIsInNpdGUiOiJodHRwczovL29wcy1jb2ZmZWUuY24ifQ.fIpSXy476r9F9i7GhdYFNkd-2Ndz8uKLgJPcd84BkJ4'JWT生成的Token是一個(gè)用兩個(gè)點(diǎn)(.)分割的長(zhǎng)字符串
點(diǎn)分割成的三部分分別是Header頭部,Payload負(fù)載,Signature簽名:Header.Payload.Signature
JWT是不加密的,任何人都可以讀的到其中的信息,其中第一部分Header和第二部分Payload只是對(duì)原始輸入的信息轉(zhuǎn)成了base64編碼,第三部分Signature是用header+payload+secret_key進(jìn)行加密的結(jié)果
可以直接用base64對(duì)Header和Payload進(jìn)行解碼得到相應(yīng)的信息
>>> import base64 >>> base64.b64decode('eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9') b'{"typ":"JWT","alg":"HS256"}'>>> base64.b64decode('eyJ1c2VybmFtZSI6Ilx1OGZkMFx1N2VmNFx1NTQ5Nlx1NTU2MVx1NTQyNyIsInNpdGUiOiJodHRwczovL29wcy1jb2ZmZWUuY24ifQ==') # 這里最后加=的原因是base64解碼對(duì)傳入的參數(shù)長(zhǎng)度不是2的對(duì)象,需要再參數(shù)最后加上一個(gè)或兩個(gè)等號(hào)=因?yàn)镴WT不會(huì)對(duì)結(jié)果進(jìn)行加密,所以不要保存敏感信息在Header或者Payload中,服務(wù)端也主要依靠最后的Signature來(lái)驗(yàn)證Token是否有效以及有無(wú)被篡改
解密Token
>>> jwt.decode(encoded_jwt,'secret_key',algorithms=['HS256']) {'username': '運(yùn)維咖啡吧', 'site': 'https://ops-coffee.cn'}服務(wù)端在有秘鑰的情況下可以直接對(duì)JWT生成的Token進(jìn)行解密,解密成功說(shuō)明Token正確,且數(shù)據(jù)沒(méi)有被篡改
當(dāng)然我們前文說(shuō)了JWT并沒(méi)有對(duì)數(shù)據(jù)進(jìn)行加密,如果沒(méi)有secret_key也可以直接獲取到Payload里邊的數(shù)據(jù),只是缺少了簽名算法無(wú)法驗(yàn)證數(shù)據(jù)是否準(zhǔn)確,pyjwt也提供了直接獲取Payload數(shù)據(jù)的方法,如下
>>> jwt.decode(encoded_jwt, verify=False) {'username': '運(yùn)維咖啡吧', 'site': 'https://ops-coffee.cn'}Django案例
Django要兼容session認(rèn)證的方式,還需要同時(shí)支持JWT,并且兩種驗(yàn)證需要共用同一套權(quán)限系統(tǒng),該如何處理呢?我們可以參考Django的解決方案:裝飾器,例如用來(lái)檢查用戶是否登錄的login_required和用來(lái)檢查用戶是否有權(quán)限的permission_required兩個(gè)裝飾器,我們可以自己實(shí)現(xiàn)一個(gè)裝飾器,檢查用戶的認(rèn)證模式,同時(shí)認(rèn)證完成后驗(yàn)證用戶是否有權(quán)限操作
于是一個(gè)auth_permission_required的裝飾器產(chǎn)生了:
from django.conf import settings from django.http import JsonResponse from django.contrib.auth import get_user_model from django.core.exceptions import PermissionDeniedUserModel = get_user_model()def auth_permission_required(perm):def decorator(view_func):def _wrapped_view(request, *args, **kwargs):# 格式化權(quán)限perms = (perm,) if isinstance(perm, str) else permif request.user.is_authenticated:# 正常登錄用戶判斷是否有權(quán)限if not request.user.has_perms(perms):raise PermissionDeniedelse:try:auth = request.META.get('HTTP_AUTHORIZATION').split()except AttributeError:return JsonResponse({"code": 401, "message": "No authenticate header"})# 用戶通過(guò)API獲取數(shù)據(jù)驗(yàn)證流程if auth[0].lower() == 'token':try:dict = jwt.decode(auth[1], settings.SECRET_KEY, algorithms=['HS256'])username = dict.get('data').get('username')except jwt.ExpiredSignatureError:return JsonResponse({"status_code": 401, "message": "Token expired"})except jwt.InvalidTokenError:return JsonResponse({"status_code": 401, "message": "Invalid token"})except Exception as e:return JsonResponse({"status_code": 401, "message": "Can not get user object"})try:user = UserModel.objects.get(username=username)except UserModel.DoesNotExist:return JsonResponse({"status_code": 401, "message": "User Does not exist"})if not user.is_active:return JsonResponse({"status_code": 401, "message": "User inactive or deleted"})# Token登錄的用戶判斷是否有權(quán)限if not user.has_perms(perms):return JsonResponse({"status_code": 403, "message": "PermissionDenied"})else:return JsonResponse({"status_code": 401, "message": "Not support auth type"})return view_func(request, *args, **kwargs)return _wrapped_viewreturn decorator在view使用時(shí)就可以用這個(gè)裝飾器來(lái)代替原本的login_required和permission_required裝飾器了
@auth_permission_required('account.select_user') def user(request):if request.method == 'GET':_jsondata = {"user": "ops-coffee","site": "https://ops-coffee.cn"}return JsonResponse({"state": 1, "message": _jsondata})else:return JsonResponse({"state": 0, "message": "Request method 'POST' not supported"})我們還需要一個(gè)生成用戶Token的方法,通過(guò)給User model添加一個(gè)token的靜態(tài)方法來(lái)處理
class User(AbstractBaseUser, PermissionsMixin):create_time = models.DateTimeField(auto_now_add=True, verbose_name='創(chuàng)建時(shí)間')update_time = models.DateTimeField(auto_now=True, verbose_name='更新時(shí)間')username = models.EmailField(max_length=255, unique=True, verbose_name='用戶名')fullname = models.CharField(max_length=64, null=True, verbose_name='中文名')phonenumber = models.CharField(max_length=16, null=True, unique=True, verbose_name='電話')is_active = models.BooleanField(default=True, verbose_name='激活狀態(tài)')objects = UserManager()USERNAME_FIELD = 'username'REQUIRED_FIELDS = []def __str__(self):return self.username@propertydef token(self):return self._generate_jwt_token()def _generate_jwt_token(self):token = jwt.encode({'exp': datetime.utcnow() + timedelta(days=1),'iat': datetime.utcnow(),'data': {'username': self.username}}, settings.SECRET_KEY, algorithm='HS256')return token.decode('utf-8')class Meta:default_permissions = ()permissions = (("select_user", "查看用戶"),("change_user", "修改用戶"),("delete_user", "刪除用戶"),)可以直接通過(guò)用戶對(duì)象來(lái)生成Token:
>>> from accounts.models import User >>> u = User.objects.get(username='admin@ops-coffee.cn') >>> u.token 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NDgyMjg3NzksImlhdCI6MTU0ODE0MjM3OSwiZGF0YSI6eyJ1c2VybmFtZSI6ImFkbWluQDE2My5jb20ifX0.akZNU7t_z2kwPxDJjmc-QxtNdICK0yhnwWmKxqqXKLw'生成的Token給到客戶端,客戶端就可以拿這個(gè)Token進(jìn)行鑒權(quán)了
>>> import requests >>> token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NDgyMjg4MzgsImlhdCI6MTU0ODE0MjQzOCwiZGF0YSI6eyJ1c2VybmFtZSI6ImFkbWluQDE2My5jb20ifX0.oKc0SafgksMT9ZIhTACupUlz49Q5kI4oJA-B8-GHqLA' >>> >>> r = requests.get('http://localhost/api/user', headers={'Authorization': 'Token '+token}) >>> r.json() {'username': 'admin@ops-coffee.cn', 'fullname': '運(yùn)維咖啡吧', 'is_active': True}這樣一個(gè)auth_permission_required方法就可以搞定上邊的全部需求了,簡(jiǎn)單好用。
如果你覺(jué)得文章對(duì)你有幫助,請(qǐng)點(diǎn)右下角【好看】。如果你覺(jué)得讀的不盡興,推薦閱讀以下文章:
- Django+Echarts畫圖實(shí)例
- Django開(kāi)發(fā)密碼管理表實(shí)例【附源碼】
轉(zhuǎn)載于:https://www.cnblogs.com/37Y37/p/10305437.html
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的Django+JWT实现Token认证的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 设计模式之委派模式及适配器模式
- 下一篇: python + MySql 基本操作