JWT驗證機制

1、JWT:javascript

在用戶註冊或登陸後,咱們想記錄用戶的登陸狀態,或者爲用戶建立身份認證的憑證。咱們再也不使用Session認證機制,而使用Json Web Token認證機制。
1.什麼是JWT:html

Json web token (JWT), 是爲了在網絡應用環境間傳遞聲明而執行的一種基於JSON的開放標準((RFC 7519).該token被設計爲緊湊且安全的,特別適用於分佈式站點的單點登陸(SSO)場景。JWT的聲明通常被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便於從資源服務器獲取資源,也能夠增長一些額外的其它業務邏輯所必須的聲明信息,該token也可直接被用於認證,也可被加密。
1.1 起源:前端

提及JWT,咱們應該來談一談基於token的認證和傳統的session認證的區別。java

1.1.1 傳統的session認證:ios

咱們知道,http協議自己是一種無狀態的協議,而這就意味着若是用戶向咱們的應用提供了用戶名和密碼來進行用戶認證,那麼下一次請求時,用戶還要再一次進行用戶認證才行,由於根據http協議,咱們並不能知道是哪一個用戶發出的請求,因此爲了讓咱們的應用能識別是哪一個用戶發出的請求,咱們只能在服務器存儲一份用戶登陸的信息,這份登陸信息會在響應時傳遞給瀏覽器,告訴其保存爲cookie,以便下次請求時發送給咱們的應用,這樣咱們的應用就能識別請求來自哪一個用戶了,這就是傳統的基於session認證。git

可是這種基於session的認證使應用自己很可貴到擴展,隨着不一樣客戶端用戶的增長,獨立的服務器已沒法承載更多的用戶,而這時候基於session認證應用的問題就會暴露出來.
基於session認證所顯露的問題
session: 每一個用戶通過咱們的應用認證以後,咱們的應用都要在服務端作一次記錄,以方便用戶下次請求的鑑別,一般而言session都是保存在內存中,而隨着認證用戶的增多,服務端的開銷會明顯增大。
擴展性: 用戶認證以後,服務端作認證記錄,若是認證的記錄被保存在內存中的話,這意味着用戶下次請求還必需要請求在這臺服務器上,這樣才能拿到受權的資源,這樣在分佈式的應用上,相應的限制了負載均衡器的能力。這也意味着限制了應用的擴展能力。
CSRF: 由於是基於cookie來進行用戶識別的, cookie若是被截獲,用戶就會很容易受到跨站請求僞造的攻擊。github

1.1.2 基於token的鑑權機制:web

基於token的鑑權機制相似於http協議也是無狀態的,它不須要在服務端去保留用戶的認證信息或者會話信息。這就意味着基於token認證機制的應用不須要去考慮用戶在哪一臺服務器登陸了,這就爲應用的擴展提供了便利。算法

流程上是這樣的:數據庫

用戶使用用戶名密碼來請求服務器
服務器進行驗證用戶的信息
服務器經過驗證發送給用戶一個token
客戶端存儲token,並在每次請求時附送上這個token值
服務端驗證token值,並返回數據
這個token必需要在每次請求時傳遞給服務端,它應該保存在請求頭裏, 另外,服務端要支持CORS(跨來源資源共享)策略,通常咱們在服務端這麼作就能夠了Access-Control-Allow-Origin: *。

2. JWT長什麼樣?

JWT是由三段信息構成的,將這三段信息文本用.連接一塊兒就構成了Jwt字符串。就像這樣:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

2.1 JWT的構成:

第一部分咱們稱它爲頭部(header);
第二部分咱們稱其爲載荷(payload, 相似於飛機上承載的物品);
第三部分是簽證(signature).

2.1.1 header:

jwt的頭部承載兩部分信息:

聲明類型,這裏是jwt
聲明加密的算法 一般直接使用 HMAC SHA256
完整的頭部就像下面這樣的JSON:
{
  'typ': 'JWT',
  'alg': 'HS256'
}

而後將頭部被進行base64加密(該加密是能夠被對稱解密的), 構成第一部分

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

2.1.2 payload

在和就是存放有效信息的地方。這個名字像是特指飛機上承載的貨品,這些有效信息包含三個部分

標準中註冊的聲明

公共的聲明

私有的聲明
標準中註冊的聲明 (建議但不強制使用) :

iss: jwt簽發者

sub: jwt所面向的用戶

aud: 接收jwt的一方

exp: jwt的過時時間,這個過時時間必需要大於簽發時間

nbf: 定義在什麼時間以前,該jwt都是不可用的.

iat: jwt的簽發時間

jti: jwt的惟一身份標識,主要用來做爲一次性token,從而回避重放攻擊。
公共的聲明 : 公共的聲明能夠添加任何的信息,通常添加用戶的相關信息或其餘業務須要的必要信息.但不建議添加敏感信息,由於該部分在客戶端可解密.

私有的聲明 : 私有聲明是提供者和消費者所共同定義的聲明,通常不建議存放敏感信息,由於base64是對稱解密的,意味着該部分信息能夠歸類爲明文信息。

定義一個payload:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

2.1.3 signature

JWT的第三部分是一個簽證信息,這個簽證信息由三部分組成:

header (base64後的)
payload (base64後的)
secret
這個部分須要base64加密後的header和base64加密後的payload使用.鏈接組成的字符串,而後經過header中聲明的加密方式進行加鹽secret組合加密,而後就構成了jwt的第三部分。

// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);

var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

將這三部分用 . 鏈接成一個完整的字符串,構成了最終的jwt:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

3. 如何應用:

通常是在請求頭裏加入Authoriztion, 並加上Beaer標註:

fetch('api/user/1', {
 headers: {
   'Authorization': 'Bearer ' + token
}
})

服務端會驗證token,若是驗證經過就會返回相應的資源。整個流程就是這樣的:


4.總結

4.1優勢

由於json的通用性,因此JWT是能夠進行跨語言支持的,像JAVA,JavaScript,NodeJS,PHP等不少語言均可以使用。
由於有了payload部分,因此JWT能夠在自身存儲一些其餘業務邏輯所必要的非敏感信息。
便於傳輸,jwt的構成很是簡單,字節佔用很小,因此它是很是便於傳輸的。
它不須要在服務端保存會話信息, 因此它易於應用的擴展
4.2安全相關

不該該在jwt的payload部分存放敏感信息,由於該部分是客戶端可解密的部分。
保護好secret私鑰,該私鑰很是重要。
若是能夠,請使用https協議
2、Django REST framework JWT:

咱們在驗證完用戶的身份後(檢驗用戶名和密碼), 須要向用戶欠打JWT,在須要用到用戶身份信息的時候,還需覈驗用戶的JWT。
關於簽發和核驗JWT, 咱們可使用Django REST framework JWT擴展來完成。
文檔網站:http://getblimp.github.io/django-rest-framework-jwt/

1.安裝配置:

安裝:

pip install djangorestframework-jwt
配置:

REST_FRAMEWORK = {
   'DEFAULT_AUTHENTICATION_CLASSES': (
       'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
       'rest_framework.authentication.SessionAuthentication',
       'rest_framework.authentication.BasicAuthentication',
  ),
}

JWT_AUTH = {
   'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
}
# JWT_EXPIRATION_DELTA 指明token的有效期

2. 使用

Django REST framework JWT擴展的說明文檔中提供了手動簽發JWT的方法:

from rest_framework_jwt.settings import api_settings

jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER

payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)

在註冊成功後,聯通返回token,須要在註冊視圖中建立token。
修改CreateUserSerializer序列化器,在create方法中增長手動建立token的方法

from rest_framework_jwt.settings import api_settings

class CreateUserSerializer(serializers.ModelSerializer):
   """
  建立用戶序列化器
  """
  ...
   token = serializers.CharField(label='登陸狀態token', read_only=True)  # 增長token字段

   class Meta:
      ...
       fields = ('id', 'username', 'password', 'password2', 'sms_code', 'mobile', 'allow', 'token')  # 增長token
      ...

   def create(self, validated_data):
       """
      建立用戶
      """
       # 移除數據庫模型類中不存在的屬性
       del validated_data['password2']
       del validated_data['sms_code']
       del validated_data['allow']
       user = super().create(validated_data)

       # 調用django的認證系統加密密碼
       user.set_password(validated_data['password'])
       user.save()

       # 補充生成記錄登陸狀態的token
       jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
       jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
       payload = jwt_payload_handler(user)
       token = jwt_encode_handler(payload)
       user.token = token

       return user

前端保存token
咱們能夠將JWT保存在cookie中,也能夠保存在瀏覽器的本地存儲裏,咱們保存在瀏覽器本地存儲中

瀏覽器的本地存儲提供了sessionStorage 和 localStorage 兩種:

sessionStorage 瀏覽器關閉即失效
localStorage 長期有效
使用方法
sessionStorage.變量名 = 變量值 // 保存數據
sessionStorage.變量名 // 讀取數據
sessionStorage.clear() // 清除全部sessionStorage保存的數據

localStorage.變量名 = 變量值 // 保存數據
localStorage.變量名 // 讀取數據
localStorage.clear() // 清除全部localStorage保存的數據

在前端js/register.js文件中增長保存token

var vm = new Vue({
  ...
   methods: {
      ...
       on_submit: function(){
           axios.post(...)
              .then(response => {
                   // 記錄用戶的登陸狀態
                   sessionStorage.clear();
                   localStorage.clear();
                   localStorage.token = response.data.token;
                   localStorage.username = response.data.username;
                   localStorage.user_id = response.data.id;
                   location.href = '/index.html';
              })
              .catch(...)
      }
  }
})
相關文章
相關標籤/搜索