Django REST framework 之JWT認證

Json Web Token

一、JWT簡介

JWT 是一個開放標準(RFC 7519),它定義了一種用於簡潔,自包含的用於通訊雙方之間以 JSON 對象的形式安全傳遞信息的方法。JWT 可使用 HMAC 算法或者是 RSA 的公鑰密鑰對進行簽名。它具有兩個特色:html

  • 簡潔(Compact)前端

    能夠經過URL, POST 參數或者在 HTTP header 發送,由於數據量小,傳輸速度快web

  • 自包含(Self-contained)算法

    負載中包含了全部用戶所須要的信息,避免了屢次查詢數據庫shell

二、JWT 組成

  • Header 頭部

頭部包含了兩部分,token 類型和採用的加密算法數據庫

{
  "alg": "HS256",
  "typ": "JWT"
}
  • typ: (Type)類型。在JOSE Header中這是個可選參數,但這裏咱們須要指明類型是JWT
  • alg: (Algorithm)算法,必須是JWS支持的算法

它會使用 base64url編碼組成 JWT 結構的第一部分django

Base64URL 算法json

 這個算法跟 Base64 算法基本相似,是一種編碼,也就是說,它是能夠被翻譯回原來的樣子來的。它並非一種加密過程後端

JWT 做爲一個令牌(token),有些場合可能會放到 URL(好比 api.example.com/?token=xxx)。Base64 有三個字符+/=,在 URL 裏面有特殊含義,因此要被替換掉:=被省略、+替換成-/替換成_ 。這就是 Base64URL 算法。api

  • Payload 負載

這部分就是咱們存放信息的地方了,你能夠把用戶 ID 等信息放在這裏,JWT 規範裏面對這部分有進行了比較詳細的介紹,JWT 規定了7個官方字段,供選用

iss (issuer):簽發人
exp (expiration time):過時時間
sub (subject):主題
aud (audience):受衆
nbf (Not Before):生效時間
iat (Issued At):簽發時間
jti (JWT ID):編號

經常使用的有,

{
    "iss": "lee JWT",
    "iat": 1441593502,
    "exp": 1441594722,
    "aud": "www.example.com",
    "sub": "6465644@163.com"
}

一樣的,它會使用 base64url 編碼組成 JWT 結構的第二部分

  • Signature 簽名

 簽名的做用是保證 JWT 沒有被篡改過

前面兩部分都是使用 base64url 進行編碼的,即前端能夠解開知道里面的信息。Signature 須要使用編碼後的 header 和 payload 以及咱們提供的一個密鑰,這個密鑰只有服務器才知道,不能泄露給用戶,而後使用 header 中指定的簽名算法(HS256)進行簽名。

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
 secret)

算出簽名之後,把 Header、Payload、Signature 三個部分拼成一個字符串,每一個部分之間用"點"(.)分隔,就能夠返回給用戶。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjU3ZmVmMTY0ZTU0YWY2NGZmYzUzZGJkNSIsInhzcmYiOiI0ZWE1YzUwOGE2NTY2ZTc2MjQwNTQzZjhmZWIwNmZkNDU3Nzc3YmUzOTU0OWM0MDE2NDM2YWZkYTY1ZDIzMzBlIiwiaWF0IjoxNDc2NDI3OTMzfQ.PA3QjeyZSUh7H0GfE0vJaKW4LjKJuC3dVLQiY4hii8s
  • 簽名的目的

  最後一步簽名的過程,其實是對頭部以及負載內容進行簽名,防止內容被竄改。若是有人對頭部以及負載的內容解碼以後進行修改,再進行編碼,最後加上以前的簽名組合造成新的JWT的話,那麼服務器端會判斷出新的頭部和負載造成的簽名和JWT附帶上的簽名是不同的。若是要對新的頭部和負載進行簽名,在不知道服務器加密時用的密鑰的話,得出來的簽名也是不同的。

三、JWT 的使用方式

客戶端收到服務器返回的 JWT,能夠儲存在 Cookie 裏面,也能夠儲存在 localStorage。

 

此後,客戶端每次與服務器通訊,都要帶上這個 JWT。你能夠把它放在 Cookie 裏面自動發送,可是這樣不能跨域,因此更好的作法是放在 HTTP 請求的頭信息Authorization字段裏面。

  1. 首先,前端經過Web表單將本身的用戶名和密碼發送到後端的接口。這一過程通常是一個HTTP POST請求。建議的方式是經過SSL加密的傳輸(https協議),從而避免敏感信息被嗅探。

  2. 後端覈對用戶名和密碼成功後,將用戶的id等其餘信息做爲JWT Payload(負載),將其與頭部分別進行Base64編碼拼接後簽名,造成一個JWT。造成的JWT就是一個形同lll.zzz.xxx的字符串。

  3. 後端將JWT字符串做爲登陸成功的返回結果返回給前端。前端能夠將返回的結果保存在localStorage或sessionStorage上,退出登陸時前端刪除保存的JWT便可。

  4. 前端在每次請求時將JWT放入HTTP Header中的Authorization位。(解決XSS和XSRF問題)

        5.後端檢查是否存在,如存在驗證JWT的有效性。例如,檢查簽名是否正確;檢查Token是否過時;檢查Token的接收方是不是本身(可選)。

四、JWT 的幾個特色

(1)JWT 默認是不加密,但也是能夠加密的。生成原始 Token 之後,能夠用密鑰再加密一次。

(2)JWT 不加密的狀況下,不能將祕密數據寫入 JWT。

(3)JWT 不只能夠用於認證,也能夠用於交換信息。有效使用 JWT,能夠下降服務器查詢數據庫的次數。

(4)JWT 的最大缺點是,因爲服務器不保存 session 狀態,所以沒法在使用過程當中廢止某個 token,或者更改 token 的權限。也就是說,一旦 JWT 簽發了,在到期以前就會始終有效,除非服務器部署額外的邏輯。

(5)JWT 自己包含了認證信息,一旦泄露,任何人均可以得到該令牌的全部權限。爲了減小盜用,JWT 的有效期應該設置得比較短。對於一些比較重要的權限,使用時應該再次對用戶進行認證。

(6)爲了減小盜用,JWT 不該該使用 HTTP 協議明碼傳輸,要使用 HTTPS 協議傳輸。

Django REST framework 中使用 JWT認證

 使用django-rest-framework-jwt這個庫來幫助咱們簡單的使用jwt進行身份驗證
並解決一些先後端分離而產生的跨域問題

  • 安裝

直接使用pip安裝便可,目前支持Python、Django、DRF主流版本

pip install djangorestframework-jwt
  • 使用
在settings.py文件中,將JSONWebTokenAuthentication 添加到REST framework框架的DEFAULT_AUTHENTICATION_CLASSES.
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication',
    ),

  一樣,你還可使用基於APIView類的視圖,在每一個視圖或每一個視圖集的基礎上設置身份驗證方案。與 Token 認證同樣,儘量使用基於APIView類的視圖認證方式。

但使用基於APIView類的視圖認證方式時,不要忘記導入類。

from rest_framework_jwt.authentication import JSONWebTokenAuthentication

在你的urls.py文件中添加如下URL路由,以便經過POST包含用戶名和密碼的令牌獲取。

from rest_framework_jwt.views import obtain_jwt_token
urlpatterns += [
    url(r'^api-token-auth/', obtain_jwt_token)
]

若是你使用用戶名admin和密碼admin123456建立了用戶,則能夠經過在終端中執行如下操做來測試JWT是否正常工做。

$ curl -X POST -d "username=admin&password=admin123456" http://127.0.0.1:8000/api-token-auth/

或者,你可使用Django REST framework支持的全部內容類型來獲取身份驗證令牌。例如:

$ curl -X POST -H "Content-Type: application/json" -d '{"token":"<EXISTING_TOKEN>"}' http://localhost:8000/api-token-refresh/

能夠重複使用令牌刷新(token1 -> token2 -> token3),但此令牌鏈存儲原始令牌(使用用戶名/密碼憑據獲取)的時間。做爲orig_iat,你只能將刷新令牌保留至JWT_REFRESH_EXPIRATION_DELTA。

刷新token以得到新的token的做用在於,持續保持活躍用戶登陸狀態。好比經過用戶密碼得到的token有效時間爲1小時,那麼也就意味着1小時後此token失效,用戶必須得從新登陸,這對於活躍用戶來講實際上是多餘的。若是這個用戶在這1小時內都在瀏覽網站,咱們不該該讓用戶從新登陸,就是在token沒有失效以前調用刷新接口爲用戶得到新的token。

  • 認證Token

在一些微服務架構中,身份驗證由單個服務處理。此服務負責其餘服務委派確認用戶已登陸此身份驗證服務的責任。這一般意味着其餘服務將從用戶接收JWT傳遞給身份驗證服務,並在將受保護資源返回給用戶以前等待JWT有效的確認。添加如下URL模式:

from rest_framework_jwt.views import verify_jwt_token
urlpatterns += [
    url(r'^api-token-verify/', verify_jwt_token)
]

將Token傳遞給驗證API,若是令牌有效,則返回令牌,返回狀態碼爲200。不然,它將返回400 Bad Request以及識別令牌無效的錯誤。

  • 手動建立Token

有時候你可能但願手動生成令牌,例如在建立賬戶後當即將令牌返回給用戶。或者,你須要返回的信息不止是Token,可能還有用戶權限相關值。你能夠這樣作:

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過時時間,如下是全部可用設置的默認值。在settings.py文件中設置。

JWT_AUTH = {
    'JWT_ENCODE_HANDLER':
    'rest_framework_jwt.utils.jwt_encode_handler',
 
    'JWT_DECODE_HANDLER':
    'rest_framework_jwt.utils.jwt_decode_handler',
 
    'JWT_PAYLOAD_HANDLER':
    'rest_framework_jwt.utils.jwt_payload_handler',
 
    'JWT_PAYLOAD_GET_USER_ID_HANDLER':
    'rest_framework_jwt.utils.jwt_get_user_id_from_payload_handler',
 
    'JWT_RESPONSE_PAYLOAD_HANDLER':
    'rest_framework_jwt.utils.jwt_response_payload_handler',
    
    // 這是用於簽署JWT的密鑰,確保這是安全的,不共享不公開的
    'JWT_SECRET_KEY': settings.SECRET_KEY,
    'JWT_GET_USER_SECRET_KEY': None,
    'JWT_PUBLIC_KEY': None,
    'JWT_PRIVATE_KEY': None,
    'JWT_ALGORITHM': 'HS256',
    // 若是祕鑰是錯誤的,它會引起一個jwt.DecodeError
    'JWT_VERIFY': True,
    'JWT_VERIFY_EXPIRATION': True,
    'JWT_LEEWAY': 0,
    // Token過時時間設置
    'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=300),
    'JWT_AUDIENCE': None,
    'JWT_ISSUER': None,
    
    // 是否開啓容許Token刷新服務,及限制Token刷新間隔時間,從原始Token獲取開始計算
    'JWT_ALLOW_REFRESH': False,
    'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7),
 
    // 定義與令牌一塊兒發送的Authorization標頭值前綴
    'JWT_AUTH_HEADER_PREFIX': 'JWT',
    'JWT_AUTH_COOKIE': None,

通常除了過時時間外,其餘配置參數不多改變。具體參數意義當用到時能夠查詢官網。

 

參考:http://www.ruanyifeng.com/blog/2018/0

7/json_web_token-tutorial.html

參考:https://juejin.im/entry/5979a9355188253de4272ff4

參考:http://www.ywnds.com/?p=14967

相關文章
相關標籤/搜索