單點登入,sessions,cokkie,JWT
什麼是單點登入:
單點登入SSO(Single Sign On)說得簡單點就是在一個多系統共存的環境下,使用者在一處登入後,就不用在其他系統中登入,也就是使用者的一次登入能得到其他所有系統的信任。
如何解決單點登入:
1.session+cookie+redis
2.設定登入伺服器
3.JWT
JWT:
JWT長什麼樣?
JWT是由三段資訊構成的,將這三段資訊文字用.
連結一起就構成了Jwt字串。就像這樣:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
JWT的構成
第一部分我們稱它為頭部(header),第二部分我們稱其為載荷(payload, 類似於飛機上承載的物品),第三部分是簽證(signature).
header
j wt的頭部承載兩部分資訊:
-
宣告型別,這裡是jwt
-
宣告加密的演算法 通常直接使用 HMAC SHA256
完整的頭部就像下面這樣的JSON:
{
'typ': 'JWT',
'alg': 'HS256'
}
然後將頭部進行base64加密(該加密是可以對稱解密的),構成了第一部分.
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
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
}
然後將其進行base64加密,得到JWT的第二部分。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
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
注意:secret是儲存在伺服器端的,jwt的簽發生成也是在伺服器端的,secret就是用來進行jwt的簽發和jwt的驗證,所以,它就是你服務端的私鑰,在任何場景都不應該流露出去。一旦客戶端得知這個secret, 那就意味著客戶端是可以自我簽發jwt了。
如何應用
一般是在請求頭裡加入Authorization
,並加上Bearer
標註:
fetch('api/user/1', {
headers: {
'Authorization': 'Bearer ' + token
}
})
服務端會驗證token,如果驗證通過就會返回相應的資源。整個流程就是這樣的:
總結
優點
-
因為json的通用性,所以JWT是可以進行跨語言支援的,像JAVA,JavaScript,NodeJS,PHP等很多語言都可以使用。
-
因為有了payload部分,所以JWT可以在自身儲存一些其他業務邏輯所必要的非敏感資訊。
-
便於傳輸,jwt的構成非常簡單,位元組佔用很小,所以它是非常便於傳輸的。
-
它不需要在服務端儲存會話資訊, 所以它易於應用的擴充套件
安全相關
-
不應該在jwt的payload部分存放敏感資訊,因為該部分是客戶端可解密的部分。
-
保護好secret私鑰,該私鑰非常重要。
-
如果可以,請使用https協議
JWT使用:
安裝配置
pip install djangorestframework-jwt
配置setting
setting.py配置全域性路由
from django.contrib import admin from django.urls import path,re_path,include urlpatterns = [ path('admin/', admin.site.urls), re_path(r'users/',include(('users.urls','users'),namespace='users')) ]urls.py
配置區域性路由
#! /usr/bin/env python # -*- coding: utf-8 -*- from django.urls import path,re_path,include from users import views from rest_framework_jwt.views import obtain_jwt_token # 驗證密碼後返回token urlpatterns = [ path('v1/register/', views.RegisterView.as_view(), name='register'), # 註冊使用者 path('v1/login/', obtain_jwt_token,name='login'), # 使用者登入後返回token path('v1/list/', views.UserList.as_view(), name='register'), # 測試需要攜帶token才能訪問 ]urls.py
重寫User表
from django.db import models from django.contrib.auth.models import AbstractUser class User(AbstractUser): username = models.CharField(max_length=64, unique=True) password = models.CharField(max_length=255) phone = models.CharField(max_length=64) token = models.CharField(max_length=255)model.py
設定序列化器
#! /usr/bin/env python # -*- coding: utf-8 -*- from rest_framework_jwt.settings import api_settings from rest_framework import serializers from users.models import User class UserSerializer(serializers.Serializer): username = serializers.CharField() password = serializers.CharField() phone = serializers.CharField() token = serializers.CharField(read_only=True) def create(self, data): user = User.objects.create(**data) user.set_password(data.get('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 userser.py
程式碼實現
from django.shortcuts import render import json from rest_framework.views import APIView from rest_framework.views import Response from rest_framework.permissions import IsAuthenticated from rest_framework_jwt.authentication import JSONWebTokenAuthentication from users.serializers import UserSerializer # 使用者註冊 class RegisterView(APIView): def post(self, request, *args, **kwargs): serializer = UserSerializer(data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=201) return Response(serializer.errors, status=400) # 重新使用者登入返回函式 def jwt_response_payload_handler(token, user=None, request=None): ''' :param token: jwt生成的token值 :param user: User物件 :param request: 請求 ''' return { 'token': token, 'user': user.username, 'userid': user.id } # 測試必須攜帶token才能訪問介面 class UserList(APIView): permission_classes = [IsAuthenticated] # 介面中加許可權 authentication_classes = [JSONWebTokenAuthentication] def get(self,request, *args, **kwargs): print(request.META.get('HTTP_AUTHORIZATION', None)) return Response({'name':'zhangsan'}) def post(self,request, *args, **kwargs): return Response({'name':'zhangsan'})view.py
區別和優缺點:
基於session和基於jwt的方式的主要區別就是使用者的狀態儲存的位置,session是儲存在服務端的,而jwt是儲存在客戶端的。
jwt的優點:
-
可擴充套件性好
應用程式分散式部署的情況下,session需要做多機資料共享,通常可以存在資料庫或者redis裡面。而jwt不需要。
-
無狀態
jwt不在服務端儲存任何狀態。RESTful API的原則之一是無狀態,發出請求時,總會返回帶有引數的響應,不會產生附加影響。使用者的認證狀態引入這種附加影響,這破壞了這一原則。另外jwt的載荷中可以儲存一些常用資訊,用於交換資訊,有效地使用 JWT,可以降低伺服器查詢資料庫的次數。
jwt的缺點:
-
安全性
由於jwt的payload是使用base64編碼的,並沒有加密,因此jwt中不能儲存敏感資料。而session的資訊是存在服務端的,相對來說更安全。
-
效能
jwt太長。由於是無狀態使用JWT,所有的資料都被放到JWT裡,如果還要進行一些資料交換,那載荷會更大,經過編碼之後導致jwt非常長,cookie的限制大小一般是4k,cookie很可能放不下,所以jwt一般放在local storage裡面。並且使用者在系統中的每一次http請求都會把jwt攜帶在Header裡面,http請求的Header可能比Body還要大。而sessionId只是很短的一個字串,因此使用jwt的http請求比使用session的開銷大得多。
-
一次性