1. 程式人生 > 實用技巧 >單點登入,sessions,cokkie,JWT

單點登入,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 user
ser.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的優點:

  1. 可擴充套件性好

應用程式分散式部署的情況下,session需要做多機資料共享,通常可以存在資料庫或者redis裡面。而jwt不需要。

  1. 無狀態

jwt不在服務端儲存任何狀態。RESTful API的原則之一是無狀態,發出請求時,總會返回帶有引數的響應,不會產生附加影響。使用者的認證狀態引入這種附加影響,這破壞了這一原則。另外jwt的載荷中可以儲存一些常用資訊,用於交換資訊,有效地使用 JWT,可以降低伺服器查詢資料庫的次數。

jwt的缺點:

  1. 安全性

由於jwt的payload是使用base64編碼的,並沒有加密,因此jwt中不能儲存敏感資料。而session的資訊是存在服務端的,相對來說更安全。

  1. 效能

jwt太長。由於是無狀態使用JWT,所有的資料都被放到JWT裡,如果還要進行一些資料交換,那載荷會更大,經過編碼之後導致jwt非常長,cookie的限制大小一般是4k,cookie很可能放不下,所以jwt一般放在local storage裡面。並且使用者在系統中的每一次http請求都會把jwt攜帶在Header裡面,http請求的Header可能比Body還要大。而sessionId只是很短的一個字串,因此使用jwt的http請求比使用session的開銷大得多。

  1. 一次性

無狀態是jwt的特點,但也導致了這個問題,jwt是一次性的。想修改裡面的內容,就必須簽發一個新的jwt。