JWT 是一個開放標準(RFC 7519),它定義了一種用於簡潔,自包含的用於通訊雙方之間以 JSON 對象的形式安全傳遞信息的方法。JWT 可使用 HMAC 算法或者是 RSA 的公鑰密鑰對進行簽名。它具有兩個特色:html
簡潔(Compact)前端
能夠經過URL, POST 參數或者在 HTTP header 發送,由於數據量小,傳輸速度快web
自包含(Self-contained)算法
負載中包含了全部用戶所須要的信息,避免了屢次查詢數據庫shell
頭部包含了兩部分,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
這部分就是咱們存放信息的地方了,你能夠把用戶 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 結構的第二部分
簽名的做用是保證 JWT 沒有被篡改過
前面兩部分都是使用 base64url 進行編碼的,即前端能夠解開知道里面的信息。Signature 須要使用編碼後的 header 和 payload 以及咱們提供的一個密鑰,這個密鑰只有服務器才知道,不能泄露給用戶,而後使用 header 中指定的簽名算法(HS256)進行簽名。
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
算出簽名之後,把 Header、Payload、Signature 三個部分拼成一個字符串,每一個部分之間用"點"(.
)分隔,就能夠返回給用戶。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjU3ZmVmMTY0ZTU0YWY2NGZmYzUzZGJkNSIsInhzcmYiOiI0ZWE1YzUwOGE2NTY2ZTc2MjQwNTQzZjhmZWIwNmZkNDU3Nzc3YmUzOTU0OWM0MDE2NDM2YWZkYTY1ZDIzMzBlIiwiaWF0IjoxNDc2NDI3OTMzfQ.PA3QjeyZSUh7H0GfE0vJaKW4LjKJuC3dVLQiY4hii8s
最後一步簽名的過程,其實是對頭部以及負載內容進行簽名,防止內容被竄改。若是有人對頭部以及負載的內容解碼以後進行修改,再進行編碼,最後加上以前的簽名組合造成新的JWT的話,那麼服務器端會判斷出新的頭部和負載造成的簽名和JWT附帶上的簽名是不同的。若是要對新的頭部和負載進行簽名,在不知道服務器加密時用的密鑰的話,得出來的簽名也是不同的。
客戶端收到服務器返回的 JWT,能夠儲存在 Cookie 裏面,也能夠儲存在 localStorage。
此後,客戶端每次與服務器通訊,都要帶上這個 JWT。你能夠把它放在 Cookie 裏面自動發送,可是這樣不能跨域,因此更好的作法是放在 HTTP 請求的頭信息Authorization
字段裏面。
首先,前端經過Web表單將本身的用戶名和密碼發送到後端的接口。這一過程通常是一個HTTP POST請求。建議的方式是經過SSL加密的傳輸(https協議),從而避免敏感信息被嗅探。
後端覈對用戶名和密碼成功後,將用戶的id等其餘信息做爲JWT Payload(負載),將其與頭部分別進行Base64編碼拼接後簽名,造成一個JWT。造成的JWT就是一個形同lll.zzz.xxx的字符串。
後端將JWT字符串做爲登陸成功的返回結果返回給前端。前端能夠將返回的結果保存在localStorage或sessionStorage上,退出登陸時前端刪除保存的JWT便可。
前端在每次請求時將JWT放入HTTP Header中的Authorization位。(解決XSS和XSRF問題)
5.後端檢查是否存在,如存在驗證JWT的有效性。例如,檢查簽名是否正確;檢查Token是否過時;檢查Token的接收方是不是本身(可選)。
(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這個庫來幫助咱們簡單的使用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 '{"username":"admin","password":"admin123456"}' http://127.0.0.1:8000/api-token-auth/
如今訪問須要認證的API時,就必需要包含Authorization: JWT <your_token> 頭信息了:
$ curl -H "Authorization: JWT <your_token>" http://127.0.0.1:8000/virtual/
若是JWT_ALLOW_REFRESH
爲True,能夠「刷新」未過時的令牌以得到具備更新到期時間的全新令牌。像以下這樣添加一個URL模式:
from rest_framework_jwt.views import refresh_jwt_token urlpatterns += [ url(r'^api-token-refresh/', refresh_jwt_token) ]
使用方式就是將現有令牌傳遞到刷新API,以下所示: {"token": EXISTING_TOKEN}
。請注意,只有非過時的令牌纔有效。另外,響應JSON看起來與正常獲取令牌端點{"token": NEW_TOKEN}
相同。
$ 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。
在一些微服務架構中,身份驗證由單個服務處理。此服務負責其餘服務委派確認用戶已登陸此身份驗證服務的責任。這一般意味着其餘服務將從用戶接收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,可能還有用戶權限相關值。你能夠這樣作:
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