1. JWT
JSON Web Token (JWT)是一個開放標准(RFC 7519),它定義了一種緊湊的、自包含的方式,用於作為JSON對象在各方之間安全地傳輸信息。該信息可以被驗證和信任,因為它是數字簽名的。
1.2 什么時候你應該用JSON Web Token
下列場景中使用JSON Web Token是很有用的:
-
Authorization (授權) : 這是使用JWT的最常見場景。一旦用戶登錄,后續每個請求都將包含JWT,允許用戶訪問該令牌允許的路由、服務和資源。單點登錄是現在廣泛使用的JWT的一個特性,因為它的開銷很小,並且可以輕松地跨域使用。
-
Information Exchange (信息交換) : 對於安全的在各方之間傳輸信息而言,JSON Web Tokens無疑是一種很好的方式。因為JWT可以被簽名,例如,用公鑰/私鑰對,你可以確定發送人就是它們所說的那個人。另外,由於簽名是使用頭和有效負載計算的,您還可以驗證內容沒有被篡改。
1.3 JSON Web Token的結構是什么樣的
JSON Web Token由三部分組成,它們之間用圓點(.)連接。這三部分分別是:
-
Header
-
Payload
-
Signature
因此,一個典型的JWT看起來是這個樣子的:
xxxxx.yyyyy.zzzzz
接下來,具體看一下每一部分:
-
Header header典型的由兩部分組成:token的類型(“JWT”)和算法名稱(比如:HMAC SHA256或者RSA等等)。
例如:
{
'alg': "HS256",
'typ': "JWT"
}
然后,用Base64對這個JSON編碼就得到JWT的第一部分
-
Payload JWT的第二部分是payload,它包含聲明(要求)。聲明是關於實體(通常是用戶)和其他數據的聲明。聲明有三種類型: registered, public 和 private。
-
-
Registered claims : 這里有一組預定義的聲明,它們不是強制的,但是推薦。比如:iss (issuer), exp (expiration time), sub (subject), aud (audience)等。
-
Public claims : 可以隨意定義。
-
Private claims : 用於在同意使用它們的各方之間共享信息,並且不是注冊的或公開的聲明。 下面是一個例子:
-
{
"sub": '1234567890',
"name": 'john',
"admin":true
}
對payload進行Base64編碼就得到JWT的第二部分
注意,不要在JWT的payload或header中放置敏感信息,除非它們是加密的。
-
Signature
為了得到簽名部分,你必須有編碼過的header、編碼過的payload、一個秘鑰,簽名算法是header中指定的那個,然對它們簽名即可。
例如:
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
簽名是用於驗證消息在傳遞過程中有沒有被更改,並且,對於使用私鑰簽名的token,它還可以驗證JWT的發送方是否為它所稱的發送方。
看一張官網的圖就明白了:
1.4 JSON Web Tokens是如何工作的
在認證的時候,當用戶登錄成功后生成一個JSON Web Token將會被返回。下次訪問在頭部帶上token
header應該看起來是這樣的:
Authorization: Bearer
服務器上的受保護的路由將會檢查Authorization header中的JWT是否有效,如果有效,則用戶可以訪問受保護的資源。
1.5 基於Token的身份認證 與 基於服務器的身份認證
基於服務器的身份認證
在討論基於Token的身份認證是如何工作的以及它的好處之前,我們先來看一下以前我們是怎么做的:
HTTP協議是無狀態的,也就是說,如果我們已經認證了一個用戶,那么他下一次請求的時候,服務器不知道我是誰,我們必須再次認證
傳統的做法是將已經認證過的用戶信息存儲在服務器上,比如Session。用戶下次請求的時候帶着Session ID,然后服務器以此檢查用戶是否認證過。
這種基於服務器的身份認證方式存在一些問題:
-
Sessions : 每次用戶認證通過以后,服務器需要創建一條記錄保存用戶信息,通常是在內存中,隨着認證通過的用戶越來越多,服務器的在這里的開銷就會越來越大。
-
Scalability : 由於Session是在內存中的,這就帶來一些擴展性的問題。
-
CORS : 當我們想要擴展我們的應用,讓我們的數據被多個移動設備使用時,我們必須考慮跨資源共享問題。當使用AJAX調用從另一個域名下獲取資源時,我們可能會遇到禁止請求的問題。
-
CSRF : 用戶很容易受到CSRF攻擊。
JWT與Session的差異 相同點是,它們都是存儲用戶信息;然而,Session是在服務器端的,而JWT是在客戶端的。
Session方式存儲用戶信息的最大問題在於要占用大量服務器內存,增加服務器的開銷。
而JWT方式將用戶狀態分散到了客戶端中,可以明顯減輕服務端的內存壓力。
Session的狀態是存儲在服務器端,客戶端只有session id;而Token的狀態是存儲在客戶端。
基於Token的身份認證是如何工作的
基於Token的身份認證是無狀態的,服務器或者Session中不會存儲任何用戶信息。
沒有會話信息意味着應用程序可以根據需要擴展和添加更多的機器,而不必擔心用戶登錄的位置。
雖然這一實現可能會有所不同,但其主要流程如下:
-用戶攜帶用戶名和密碼請求訪問 -服務器校驗用戶憑據 -應用提供一個token給客戶端 -客戶端存儲token,並且在隨后的每一次請求中都帶着它 -服務器校驗token並返回數據
注意:
-每一次請求都需要token -Token應該放在請求header中 -我們還需要將服務器設置為接受來自所有域的請求,用Access-Control-Allow-Origin: *
用Token的好處
無狀態和可擴展性:Tokens存儲在客戶端。完全無狀態,可擴展。我們的負載均衡器可以將用戶傳遞到任意服務器,因為在任何地方都沒有狀態或會話信息。
-
安全:Token不是Cookie。(The token, not a cookie.)每次請求的時候Token都會被發送。而且,由於沒有Cookie被發送,還有助於防止CSRF攻擊。即使在你的實現中將token存儲到客戶端的Cookie中,這個Cookie也只是一種存儲機制,而非身份認證機制。沒有基於會話的信息可以操作,因為我們沒有會話!
還有一點,token在一段時間以后會過期,這個時候用戶需要重新登錄。這有助於我們保持安全。還有一個概念叫token撤銷,它允許我們根據相同的授權許可使特定的token甚至一組token無效。
1.6 安裝
pip install djangorestframework-jwt
1.7 在settings.py中進行配置
REST_FRAMEWORK = {
# 身份認證
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
),
}
import datetime
JWT_AUTH = {
# jwt過期時間
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
}
1.8 設置路由
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
path('login/', obtain_jwt_token),
]
#初始化管理員
python manage.py createsuperuser
請求地址: http://127.0.0.1:8000/users/login/
請求方式: post
請求參數:表單
參數名 | 類型 | 是否必須 | 說明 |
---|---|---|---|
username | str | 是 | 用戶名 |
password | str | 是 | 密碼 |
返回數據: JSON
{
"username": "zhangsan",
"user_id": 1,
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjo5LCJ1c2VybmFtZSI6InB5dGhvbjgiLCJleHAiOjE1MjgxODI2MzQsImVtYWlsIjoiIn0.ejjVvEWxrBvbp18QIjQbL1TFE0c0ejQgizui_AROlAU"
}
返回數據說明
返回值 | 類型 | 是否必須 | 說明 |
---|---|---|---|
username | str | 是 | 用戶名 |
id | int | 是 | 用戶id |
token | str | 是 | 身份認證憑據 |
djangorestframework-jwt默認只返回token
1.9 給接口添加認證
#單個添加
from rest_framework.permissions import IsAuthenticated
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
#添加獲取
class DeptManage(APIView):
permission_classes = (IsAuthenticated, )
# 標記需要進行jwt驗證
authentication_classes = (JSONWebTokenAuthentication, )
1.10 全局添加
#全局添加
REST_FRAMEWORK = {
# 身份認證
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
),
# 權限配置, 順序靠上的嚴格
'DEFAULT_PERMISSION_CLASSES':[
# 'rest_framework.permissions.IsAdminUser', # 管理員可以訪問
'rest_framework.permissions.IsAuthenticated', # 全局配置只有認證用戶可以訪問接口
# 'rest_framework.permissions.IsAuthenticatedOrReadOnly', # 認證用戶可以訪問, 否則只能讀取
# 'rest_framework.permissions.AllowAny', # 所有用戶都可以訪問
],
}
1.11 自定義返回數據
#新建utils.py文件,復制以下代碼
def jwt_response_payload_handler(token, user=None, request=None, role=None):
"""
自定義jwt認證成功返回數據
:token 返回的jwt
:user 當前登錄的用戶信息[對象]
:request 當前本次客戶端提交過來的數據
:role 角色
"""
if user.first_name:
name = user.first_name
else:
name = user.username
return {
'authenticated': 'true',
'id': user.id,
"role": role,
'name': name,
'username': user.username,
'email': user.email,
'token': token,
}
1.10 修改settings.py中的配置
# JWT配置
JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
# 自定義返回數據
'JWT_RESPONSE_PAYLOAD_HANDLER': 'meiduo.utils.jwt_response_payload_handler',
}
1.11 vue請求中加請求頭
var token = 'jwt '+'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNjE3MzcyMjQyLCJlbWFpbCI6ImFkbWluQHNmbi5jbiJ9._cZJgc77RjbMfeec1pOWyTRRx69EEFowqho9j_rWOqA'
this.axios.get('/api/cateManage/',{headers: { "Authorization": token }}).then(res=>{
console.log(res)
this.goodsList = res.data
// if(res.data.code == 200){
// this.catelist = res.data.catelist
// }
})