Django基于JWT实现微信小程序的登录和鉴权
什么是JWT?
JWT,全稱Json Web Token,用于作為JSON對(duì)象在各方之間安全地傳輸信息。該信息可以被驗(yàn)證和信任,因?yàn)樗菙?shù)字簽名的。
與Session的區(qū)別
一、Session是在服務(wù)器端的,而JWT是在客戶端的,這點(diǎn)很重要。
二、流程不同:
JWT使用場(chǎng)景
- 大量需要進(jìn)行跨域的站點(diǎn)
- 服務(wù)器運(yùn)算能力較差、存儲(chǔ)空間較小
JWT的原理
JWT 的原理是,服務(wù)器認(rèn)證以后,生成一個(gè) JSON 對(duì)象,發(fā)回給用戶,就像下面這樣。
{"姓名": "張三","角色": "管理員","到期時(shí)間": "2018年7月1日0點(diǎn)0分" }以后,用戶與服務(wù)端通信的時(shí)候,都要發(fā)回這個(gè) JSON 對(duì)象。服務(wù)器完全只靠這個(gè)對(duì)象認(rèn)定用戶身份。為了防止用戶篡改數(shù)據(jù),服務(wù)器在生成這個(gè)對(duì)象的時(shí)候,會(huì)加上簽名(詳見(jiàn)后文)。
服務(wù)器就不保存任何 session 數(shù)據(jù)了,也就是說(shuō),服務(wù)器變成無(wú)狀態(tài)了,從而比較容易實(shí)現(xiàn)擴(kuò)展。
JWT數(shù)據(jù)的格式
實(shí)際的 JWT 大概就像下面這樣。
它是一個(gè)很長(zhǎng)的字符串,中間用點(diǎn)(.)分隔成三個(gè)部分。注意,JWT 內(nèi)部是沒(méi)有換行的,這里只是為了便于展示,將它寫(xiě)成了幾行。
JWT 的三個(gè)部分依次如下。
- Header(頭部)
- Payload(負(fù)載)
- Signature(簽名)
Header
Header 部分是一個(gè) JSON 對(duì)象,描述 JWT 的元數(shù)據(jù),通常是下面的樣子。
{"alg": "HS256","typ": "JWT" }上面代碼中,alg屬性表示簽名的算法(algorithm),默認(rèn)是 HMAC SHA256(寫(xiě)成 HS256);typ屬性表示這個(gè)令牌(token)的類(lèi)型(type),JWT 令牌統(tǒng)一寫(xiě)為JWT。
最后,將上面的 JSON 對(duì)象使用 Base64URL 算法(詳見(jiàn)后文)轉(zhuǎn)成字符串。
Payload
Payload 部分也是一個(gè) JSON 對(duì)象,用來(lái)存放實(shí)際需要傳遞的數(shù)據(jù)。JWT 規(guī)定了7個(gè)官方字段,供選用。
- iss (issuer):簽發(fā)人
- exp (expiration time):過(guò)期時(shí)間
- sub (subject):主題
- aud (audience):受眾
- nbf (Not Before):生效時(shí)間
- iat (Issued At):簽發(fā)時(shí)間
- jti (JWT ID):編號(hào)
除了官方字段,你還可以在這個(gè)部分定義私有字段,下面就是一個(gè)例子。
{"sub": "1234567890","name": "John Doe","admin": true }注意,JWT 默認(rèn)是不加密的,任何人都可以讀到,所以不要把秘密信息放在這個(gè)部分。
這個(gè) JSON 對(duì)象也要使用 Base64URL 算法轉(zhuǎn)成字符串。
Signature
Signature 部分是對(duì)前兩部分的簽名,防止數(shù)據(jù)篡改。
首先,需要指定一個(gè)密鑰(secret)。這個(gè)密鑰只有服務(wù)器才知道,不能泄露給用戶。然后,使用 Header 里面指定的簽名算法(默認(rèn)是 HMAC SHA256),按照下面的公式產(chǎn)生簽名。
HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)算出簽名以后,把 Header、Payload、Signature 三個(gè)部分拼成一個(gè)字符串,每個(gè)部分之間用"點(diǎn)"(.)分隔,就可以返回給用戶。
Base64URL
前面提到,Header 和 Payload 串型化的算法是 Base64URL。這個(gè)算法跟 Base64 算法基本類(lèi)似,但有一些小的不同。
JWT 作為一個(gè)令牌(token),有些場(chǎng)合可能會(huì)放到 URL(比如 api.example.com/?token=xxx)。Base64 有三個(gè)字符+、/和=,在 URL 里面有特殊含義,所以要被替換掉:=被省略、+替換成-,/替換成_ 。這就是 Base64URL 算法。
JWT的使用方式
客戶端收到服務(wù)器返回的 JWT,可以儲(chǔ)存在 Cookie 里面,也可以儲(chǔ)存在 localStorage。
此后,客戶端每次與服務(wù)器通信,都要帶上這個(gè) JWT。你可以把它放在 Cookie 里面自動(dòng)發(fā)送,但是這樣不能跨域,所以更好的做法是放在 HTTP 請(qǐng)求的頭信息Authorization字段里面。
Authorization: Bearer <token>另一種做法是,跨域的時(shí)候,JWT 就放在 POST 請(qǐng)求的數(shù)據(jù)體里面。
JWT 的幾個(gè)特點(diǎn)
(1)JWT 默認(rèn)是不加密,但也是可以加密的。生成原始 Token 以后,可以用密鑰再加密一次。
(2)JWT 不加密的情況下,不能將秘密數(shù)據(jù)寫(xiě)入 JWT。
(3)JWT 不僅可以用于認(rèn)證,也可以用于交換信息。有效使用 JWT,可以降低服務(wù)器查詢數(shù)據(jù)庫(kù)的次數(shù)。
(4)JWT 的最大缺點(diǎn)是,由于服務(wù)器不保存 session 狀態(tài),因此無(wú)法在使用過(guò)程中廢止某個(gè) token,或者更改 token 的權(quán)限。也就是說(shuō),一旦 JWT 簽發(fā)了,在到期之前就會(huì)始終有效,除非服務(wù)器部署額外的邏輯。
(5)JWT 本身包含了認(rèn)證信息,一旦泄露,任何人都可以獲得該令牌的所有權(quán)限。為了減少盜用,JWT 的有效期應(yīng)該設(shè)置得比較短。對(duì)于一些比較重要的權(quán)限,使用時(shí)應(yīng)該再次對(duì)用戶進(jìn)行認(rèn)證。
(6)為了減少盜用,JWT 不應(yīng)該使用 HTTP 協(xié)議明碼傳輸,要使用 HTTPS 協(xié)議傳輸。
內(nèi)容說(shuō)明
以上主要內(nèi)容轉(zhuǎn)載于廖雪峰的網(wǎng)絡(luò)日志
實(shí)例:使用Django完成微信小程序的JWT登錄及鑒權(quán)
網(wǎng)上目前已經(jīng)有了一些文章來(lái)說(shuō)明了,可是我在查閱的時(shí)候發(fā)現(xiàn)大多講得不是很清楚。
基本了解
通過(guò)之前的內(nèi)容鋪墊,相信讀者對(duì)于JWT都有了一定的了解,總的來(lái)說(shuō),便是JWT是保存在用戶端的Token機(jī)制。
需要注意的是:JWT默認(rèn)是無(wú)加密的,只是使用了一層Base64編碼,所以我們不能將重要信息,如密碼等放入header和payload字段中。
開(kāi)始
對(duì)于Django來(lái)說(shuō),這里我們使用djangorestframework-jwt庫(kù)
安裝命令:
pip install djangorestframework-jwt注意djangorestframework-jwt庫(kù)默認(rèn)將settings里的SECRET_KEY當(dāng)中jwt加密秘鑰。
首先我們先去我們的project下的settings文件內(nèi)設(shè)置jwt庫(kù)的一些參數(shù)
import datetime# 在末尾添加上 REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ('rest_framework_jwt.authentication.JSONWebTokenAuthentication',# JWT認(rèn)證,在前面的認(rèn)證方案優(yōu)先'rest_framework.authentication.SessionAuthentication','rest_framework.authentication.BasicAuthentication',), } JWT_AUTH = {'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1), #JWT_EXPIRATION_DELTA 指明token的有效期 }登錄函數(shù)的實(shí)現(xiàn):
''' 登錄函數(shù): ''' def get_user_info_func(user_code):api_url = 'https://api.weixin.qq.com/sns/jscode2session?appid={0}&secret={1}&js_code={2}&grant_type=authorization_code'get_url = api_url.format(App_id,App_secret,user_code)r = requests.get(get_url)return r.json()@require_http_methods(['POST']) def user_login_func(request):try:user_code = request.POST.get('user_code')print(user_code)if user_code == None:print(request.body)json_data = json.loads(request.body)user_code = json_data['user_code']print(user_code)except:return JsonResponse({'status':500,'error':'請(qǐng)輸入完整數(shù)據(jù)'})try:json_data = get_user_info_func(user_code)#json_data = {'errcode':0,'openid':'111','session_key':'test'}if 'errcode' in json_data:return JsonResponse({'status': 500, 'error': '驗(yàn)證錯(cuò)誤:' + json_data['errmsg']})res = login_or_create_account(json_data)return JsonResponse(res)except:return JsonResponse({'status':500,'error':'無(wú)法與微信驗(yàn)證端連接'})def login_or_create_account(json_data):openid = json_data['openid']session_key = json_data['session_key']try:user = User.objects.get(username=openid)except:user = User.objects.create(username=openid,password=openid,)user.session_key = session_keyuser.save()try:jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLERjwt_encode_handler = api_settings.JWT_ENCODE_HANDLERpayload = jwt_payload_handler(user)token = jwt_encode_handler(payload)res = {'status': 200,'token': token}except:res = {'status': 500,'error': 'jwt驗(yàn)證失敗'}return res視圖函數(shù):
''' 視圖樣例: ''' from django.http import JsonResponse from account.models import * from rest_framework_jwt.views import APIView from rest_framework import authentication from rest_framework.permissions import IsAuthenticated from rest_framework_jwt.authentication import JSONWebTokenAuthentication from rest_framework import permissionsclass IsOwnerOrReadOnly(permissions.BasePermission):def has_object_permission(self, request, view, obj):if request.method in ['GET','POST']:return Truereturn obj.user == request.userclass test_view(APIView):http_method_names = ['post'] #限制api的訪問(wèn)方式authentication_classes = (authentication.SessionAuthentication,JSONWebTokenAuthentication)permission_classes = (IsAuthenticated,IsOwnerOrReadOnly) #權(quán)限管理def post(self,request): #視圖函數(shù)user = request.user.usernameU = User.objects.get(username=user)json_data = json.loads(request.body)try:test = json_data['']except:return JsonResponse({'status':500,'errmsg':'參數(shù)不全'})try:U.sex = sexU.weight = weightU.height = heightU.save()except:return JsonResponse({'status': 500, 'errmsg': '數(shù)據(jù)庫(kù)錯(cuò)誤'})return JsonResponse({'status':200})urls.py:
urlpatterns = [re_path('^$', index),re_path('^login$',login), # 登錄re_path('^test$',test_view.as_view()) ]從一道CTF引發(fā)的對(duì)JWT安全的簡(jiǎn)單思考
引例
題目:2019ISCC Web6
解題關(guān)鍵:
改題的加密方式為:RS256,是一種非對(duì)稱加密,分有公鑰和私鑰。
其中:
- 私鑰加密
- 公鑰解密
當(dāng)公鑰泄露時(shí),將JWT中的Header部分算法改為對(duì)稱加密,攻擊者本地使用泄露的公司進(jìn)行Token偽造,將獲取到的Token發(fā)送給驗(yàn)證端時(shí),會(huì)使用公鑰按照Header中的算法進(jìn)行解密,而在原來(lái)的Header中算法為RS256,需要私鑰加密生成Token,可是當(dāng)我們修改為對(duì)稱加密的HS256時(shí),我們便可以成功偽造Token,且服務(wù)端也可以正常驗(yàn)證。
產(chǎn)生這一問(wèn)題的主要原因便是,HWT的Header段是可控制的,通常只經(jīng)過(guò)一層base64編碼處理,也就是說(shuō)解密算法可有用戶控制。
防御:保護(hù)公鑰不泄露,將Header段經(jīng)RSA等方法加密。
其他問(wèn)題
- 密鑰可控
- 密鑰爆破
特殊情況,不做深入,感興趣的可見(jiàn)https://www.anquanke.com/post/id/145540#h3-9
轉(zhuǎn)載于:https://www.cnblogs.com/yunen/p/10944868.html
總結(jié)
以上是生活随笔為你收集整理的Django基于JWT实现微信小程序的登录和鉴权的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 解决ASP.Net第一次访问慢的处理 I
- 下一篇: PJzhang:互联网是有国界