Day 28 JWT認證相關
1、JWT
在用戶註冊或登陸後,咱們想記錄用戶的登陸狀態,或者爲用戶建立身份認證的憑證,咱們再也不shiyongsession認證機制,而使用Json Web Token認證機制。前端
Json web token (JWT), 是爲了在網絡應用環境間傳遞聲明而執行的一種基於JSON的開放標準((RFC 7519).該token被設計爲緊湊且安全的,特別適用於分佈式站點的單點登陸(SSO)場景。JWT的聲明通常被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便於從資源服務器獲取資源,也能夠增長一些額外的其它業務邏輯所必須的聲明信息,該token也可直接被用於認證,也可被加密。python
基於token的鑑權機制
基於token的鑑權機制相似於http協議也是無狀態的,它不須要在服務端去保留用戶的認證信息或者會話信息。這就意味着基於token認證機制的應用不須要去考慮用戶在哪一臺服務器登陸了,這就爲應用的擴展提供了便利。git
流程上是這樣的:github
- 用戶使用用戶名密碼來請求服務器
- 服務器進行驗證用戶的信息
- 服務器經過驗證發送給用戶一個token
- 客戶端存儲token,並在每次請求時附送上這個token值
- 服務端驗證token值,並返回數據
這個token必需要在每次請求時傳遞給服務端,它應該保存在請求頭裏, 另外,服務端要支持CORS(跨來源資源共享)
策略,通常咱們在服務端這麼作就能夠了Access-Control-Allow-Origin: *
。web
那麼咱們如今回到JWT的主題上。算法
JWT的特色
- 體積小,於是傳輸速度快
- 傳輸方式多樣,能夠經過URL/POST參數/HTTP頭部等方式傳輸
- 嚴格的結構化。他自身在(payload中)就包含了全部與用戶相關的驗證消息,如用戶可訪問路由、訪問有效期的信息,服務器無需再去鏈接數據庫驗證信息的有效性,而且payload支持爲你的應用而定製化。
- 支持跨域驗證,能夠應用於單點登陸。
JWT的構成
JWT就是一段字符串,由三段信息構成的,將這三段信息文本用.
連接一塊兒就構成了Jwt字符串。就像這樣:數據庫
ewogICd0eXAnOiAnSldUJywKICAnYWxnJzogJ0JBU0U2NCcKfQ==.ewogICJzdWIiOiAiMTIzNDU2Nzg5MCIsCiAgIm5hbWUiOiAi5bC85Y+k5ouJ5pav6LW15ZubIiwKICAiYWRtaW4iOiBmYWxzZQp9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
Header
第一部分咱們稱它爲頭部(header),第二部分咱們稱其爲荷載(payload,承載的數據),第三部分是簽證(signature)。django
完整的頭部就像下面這樣的JSONjson
{ 'typ': 'JWT', 'alg': 'BASE64' }
而後將頭部進行base64加密(加密是對稱的,可加可解),構成了第一部分後端
ewogICd0eXAnOiAnSldUJywKICAnYWxnJzogJ0JBU0U2NCcKfQ==
Playload
這裏是存放有效信息的地方,有效信息包含三個部分
-
標準中的註冊聲明(建議但不強制使用)
- iss:jwt簽發者
- sub:jwt所面向的用戶
- aud:接收jwt的一方
- exp:jwt的過時時間,這個過時時間必須大於簽發時間
- nbf:定義在什麼時間以前,該jwt都是不可用的
- iat:jwt的簽發時間
- jti:jwt的惟一身份表示,主要用來做爲一次性token,從而回避時序攻擊。
-
公共的聲明
這裏能夠添加任何的信息,通常添加用戶的相關信息或其餘業務須要的必要信息,但不建議添加銘感信息,由於該部分在客戶端可解密
-
私有的聲明
是提供者和消費者所共同定義的聲明,通常不建議存放敏感信息,由於base64是對稱解密的,意味着該部分信息能夠歸爲明文信息。
定義一個payload:
{ "sub": "1234567890", "name": "尼古拉斯趙四", "admin": false }
而後將其進行base64加密,獲得jwt的第二部分
ewogICJzdWIiOiAiMTIzNDU2Nzg5MCIsCiAgIm5hbWUiOiAi5bC85Y+k5ouJ5pav6LW15ZubIiwKICAiYWRtaW4iOiBmYWxzZQp9
Signature
JWT的第三部分是一個簽證信息,這個簽證信息由三部分組成:
- header(base64加密後的)
- payload(base64加密後的)
- secret(加鹽)
這個部分須要base64加密後的header和base64加密後的payload使用.
鏈接組成的字符串,而後經過header中聲明的加密方式進行加鹽secret
組合加密,而後就構成了jwt的第三部分。
將這三部分用.
鏈接成一個完整的字符串,構成了最終的jwt:
ewogICd0eXAnOiAnSldUJywKICAnYWxnJzogJ0JBU0U2NCcKfQ==.ewogICJzdWIiOiAiMTIzNDU2Nzg5MCIsCiAgIm5hbWUiOiAi5bC85Y+k5ouJ5pav6LW15ZubIiwKICAiYWRtaW4iOiBmYWxzZQp9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
注意:secret是保存在服務器端的,jwt的簽發生成也是在服務器端的,secret就是用來進行jwt的簽發和jwt的驗證,因此,它就是你服務端的私鑰,在任何場景都不該該流露出去。一旦客戶端得知這個secret, 那就意味着客戶端是能夠自我簽發jwt了。
關於簽發和核驗JWT,咱們可使用Django REST framework JWT擴展來完成。
文檔網站:http://getblimp.github.io/django-rest-framework-jwt/
2、 本質原理
JWT認證算法:簽發與校驗
- jwt分三段式:頭.體.簽名 (head.payload.sgin)
- 頭和體是可逆加密,讓服務器能夠反解出user對象;簽名是不可逆加密,保證整個token的安全性的
- 頭體簽名三部分,都是採用json格式的字符串,進行加密,可逆加密通常採用base64算法,不可逆加密通常採用hash(md5)算法
- 頭中的內容是基本信息:公司信息、項目組信息、token採用的加密方式信息
{ "company": "公司信息", ... }
- 體中的內容是關鍵信息:用戶主鍵、用戶名、簽發時客戶端信息(設備號、地址)、過時時間
{ "user_id": 1, ... }
- 簽名中的內容時安全信息:頭的加密結果 + 體的加密結果 + 服務器不對外公開的安全碼 進行md5加密
{ "head": "頭的加密字符串", "payload": "體的加密字符串", "secret_key": "安全碼" }
簽發:根據登陸請求提交來的 帳號 + 密碼 + 設備信息 簽發 token
- 用基本信息存儲json字典,採用base64算法加密獲得 頭字符串
- 用關鍵信息存儲json字典,採用base64算法加密獲得 體字符串
- 用頭、體加密字符串再加安全碼信息存儲json字典,採用hash md5算法加密獲得 簽名字符串
帳號密碼就能根據User表獲得user對象,造成的三段字符串用 . 拼接成token返回給前臺
校驗:根據客戶端帶token的請求 反解出 user 對象
- 將token按 . 拆分爲三段字符串,第一段 頭加密字符串 通常不須要作任何處理
- 第二段 體加密字符串,要反解出用戶主鍵,經過主鍵從User表中就能獲得登陸用戶,過時時間和設備信息都是安全信息,確保token沒過時,且時同一設備來的
- 再用 第一段 + 第二段 + 服務器安全碼 不可逆md5加密,與第三段 簽名字符串 進行碰撞校驗,經過後才能表明第二段校驗獲得的user對象就是合法的登陸用戶
3、DRF項目的JWT認證開發流程(重點)
- 用帳號密碼訪問登陸接口,登陸接口邏輯中調用 簽發token 算法,獲得token,返回給客戶端,客戶端本身存到cookies中
- 校驗token的算法應該寫在認證類中(在認證類中調用),全局配置給認證組件,全部視圖類請求,都會進行認證校驗,因此請求帶了token,就會反解出user對象,在視圖類中用request.user就能訪問登陸的用戶
注意:登陸接口須要作 認證 + 權限 兩個局部禁用
4、base64編碼解碼
import base64 # 導入模塊 import json # 序列化 dic = { "key": "what a beautiful day!", "value": "but I still have to bring an umbrella" } # 序列化並指定編碼格式 byte_dic = json.dumps(dic).encode('utf-8') # 編碼 dic_basesf = base64.b64encode(byte_dic) print(dic_basesf) # # b'eyJrZXkiOiAid2hhdCBhIGJlYXV0aWZ1bCBkYXlcdWZmMDEiLCAidmFsdWUiOiAiYnV0IEkgc3RpbGwgaGF2ZSB0byBicmluZyBhbiB1bWJyZWxsYSJ9' # 解碼 dic_str = base64.b64decode(dic_basesf) print(dic_str) # b'{"key": "what a beautiful day\\uff01", "value": "but I still have to bring an umbrella"}'
5、DRF-jwt安裝和簡單使用
一、安裝
pip install djangorestframework-jwt
二、使用
默認使用auth的user表中建立的用戶
# urls.py from rest_framework_jwt.views import obtain_jwt_token urlpatterns = [ path('admin/', admin.site.urls), url(r'^login/', obtain_jwt_token), ]
postman測試
向後端接口發送post請求,攜帶用戶名密碼,便可看到生成的token,攜帶用戶名密碼,登錄成功返回token
三、obtain_jwt_token本質也是一個視圖類,繼承了APIView
-經過前端傳入的用戶名密碼,校驗用戶,若是校驗經過,生成token,返回 -若是校驗失敗,返回錯誤信息
四、用戶登陸才能訪問某個接口
咱們先寫了一個視圖並配置好了路由
# urls.py url(r'^text/(?P<pk>\d+)', views.TextAPI.as_view()), # views.py class TextAPI(GenericAPIView): queryset = models.Book.objects serializer_class = serializer.BookSerializer def get(self, request, *args, **kwargs): book = self.get_object() ser = self.get_serializer(book) return utils.APIResponse(data=ser.data)
這個時候未登陸 沒有token也是能夠訪問的,咱們要想加個驗證就須要使用jwt
內部的認證類,拿過來局部配置就能夠了
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
,可是隻配置他是不行的,是沒有用的,咱們仍然能夠訪問到具體數據,咱們還須要搭配一個權限類來使用from rest_framework.permissions import IsAuthenticated
from rest_framework_jwt.authentication import JSONWebTokenAuthentication from rest_framework.permissions import IsAuthenticated class TextAPI(GenericAPIView): queryset = models.Book.objects serializer_class = serializer.BookSerializer # 只配置他是不行的,不論是否登陸,都能訪問,他須要搭配一個權限類 authentication_classes = [JSONWebTokenAuthentication, ] permission_classes = [IsAuthenticated, ] def get(self, request, *args, **kwargs): book = self.get_object() ser = self.get_serializer(book) return utils.APIResponse(data=ser.data)
這是最簡單的使用,注意
下面圖片中Vaule必定要帶 jwt + 一個空格 +token
五、若是用戶攜帶了token,而且配置了JSONWebTokenAuthentication
,從request.user就能拿到當前登陸用戶,若是沒有攜帶,當前登陸用戶就是匿名用戶
注意:若是不加DRF內置的IsAuthenticated
那他登陸也能夠訪問,沒登陸也能夠訪問
六、obtain_jwt_token源碼分析
6、控制登陸接口返回的數據格式
1 控制登陸接口返回的數據格式以下 { code:100 msg:登陸成功 token:asdfasfd username:egon } 2 寫一個函數 from homework.serializer import UserReadOnlyModelSerializer def jwt_response_payload_handler(token, user=None, request=None): return { 'code': 100, 'msg': '登陸成功', 'token': token, 'user': UserReadOnlyModelSerializer(instance=user).data } 3 在setting.py中配置 import datetime JWT_AUTH = { 'JWT_RESPONSE_PAYLOAD_HANDLER': 'homework.utils.jwt_response_payload_handler', }
7、自定義基於jwt的認證類
1 本身實現基於jwt的認證類,經過認證,才能繼續訪問,通不過認證就返回錯誤 2 代碼以下 class JwtAuthentication(BaseJSONWebTokenAuthentication): def authenticate(self, request): # 認證邏輯() # token信息能夠放在請求頭中,請求地址中 # key值能夠隨意叫 # token=request.GET.get('token') token=request.META.get('HTTP_Authorization'.upper()) # 校驗token是否合法 try: payload = jwt_decode_handler(token) except jwt.ExpiredSignature: raise AuthenticationFailed('過時了') except jwt.DecodeError: raise AuthenticationFailed('解碼錯誤') except jwt.InvalidTokenError: raise AuthenticationFailed('不合法的token') user=self.authenticate_credentials(payload) return (user, token) 3 在視圖類中配置 authentication_classes = [JwtAuthentication, ]
Another
集羣:物理形態
同一個業務,部署在多個服務器上
分佈式:工做方式
一個業務分拆多個子業務,部署在不一樣的服務器上
小飯店原來只有一個廚師,切菜洗菜備料炒菜全乾。後來客人多了,廚房一個廚師忙不過來,又請了個廚師,兩個廚師都能炒同樣的菜,這兩個廚師的關係是集羣。爲了讓廚師專心炒菜,把菜作到極致,又請了個配菜師負責切菜,備菜,備料,廚師和配菜師的關係是分佈式,一個配菜師也忙不過來了,又請了個配菜師,兩個配菜師關係是集羣
反向代理
是指以代理服務器來接受internet上的鏈接請求,而後將請求轉發給內部網絡上的服務器,並將從服務器上獲得的結果返回給internet上請求鏈接的客戶端,此時代理服務器對外就表現爲一個反向代理服務器。
正向代理
它隱藏了真實的請求客戶端, 服務端不知道真實的客戶端是誰, 客戶端清求的服務都被代理服務器代替來請求。
對稱加密
指的就是加、解密使用的同是一串密鑰,因此被稱作對稱加密。對稱加密只有一個密鑰做爲私鑰。常見的對稱加密算法:DES,AES等。
優缺點
對稱加密相比非對稱加密算法來講,加解密的效率要高得多、加密速度快。可是缺陷在於對於密鑰的管理和分發上比較困難,不是很是安全,密鑰管理負擔很重。
非對稱加密
指的是加、解密使用不一樣的密鑰,一把做爲公開的公鑰,另外一把做爲私鑰。公鑰加密的信息,只有私鑰才能解密。反之,私鑰加密的信息,只有公鑰才能解密。
優缺點
安全性更高,公鑰是公開的,密鑰是本身保存的,不須要將私鑰給別人。缺點:加密和解密花費時間長、速度慢,只適合對少許數據進行加密。