DRF访问控制(RBAC)、JWT认证
- RBAC
- 什么是RBAC
- Django的內置RBAC(六表)
- 表關系
- 實操
- 登錄admin操作
- admin二次開發
- base64編碼與解碼
- JWT認證
- 為什么使用JWT 認證?
- Session機制
- JWT機制
- JWT的構成
- header
- payload
- signature
- 本質原理
- JWT認證算法:簽發與校驗
- 簽發:根據登錄請求提交來的 賬號 + 密碼 + 設備信息 簽發 token
- 校驗:根據客戶端帶token的請求 反解出 user 對象
- JWT的種類
- nonsecure JWT
- JWS
- JWE
- 為什么使用JWT 認證?
- django中快速使用JWT
- django中快速使用JWT
- 如何簽發?
- 如何認證?
- 定制簽發token返回格式
- JWT源碼分析
- 簽發源碼分析
- 認證源碼分析
- 簽發源碼內的其他兩個類
- django中快速使用JWT
- 自定義User表,簽發token
- 普通寫法,視圖類寫
- 序列化類中寫邏輯
- 自定義認證類
- 補充:HttpRequest.META
- Simple UI快速上手
- 支持django和python版本
- 安裝
- 注冊
- 模型
- admin
- 測試
- 自定義菜單
- 快速引用靜態文件配置
RBAC
-
什么是RBAC
- RBAC 是基于角色的訪問控制(Role-Based Access Control )在 RBAC 中,權限與角色相關聯,用戶通過成為適當角色的成員而得到這些角色的權限。這就極大地簡化了權限的管理。這樣管理都是層級相互依賴的,權限賦予給角色,而把角色又賦予用戶,這樣的權限設計很清楚,管理起來很方便。
- 前面我們說到三大認證,比如權限,普通用戶和管理員能夠操作的接口就不一樣,比如我們常見的視頻軟件,不開會員能看的視頻寥寥無幾,這就是權限的應用,但是這站在公司的角度是對外的權限,比如后臺管理對公司內使用,公司內部的權限該如何分配?就用到了RBAC
- 對外權限針對用戶:普通注冊用戶,會員,超級會員····
- 對內權限公司內部:使用RBAC的權限控制比如公司內部有開發部、財政部、市場部、人事部、運營部、總裁辦··· 這寫部門的權限是不同的,比如人事部有招人的權力,開發部有查看修改提交代碼的權力···所以通過將權限和角色(部門)綁定,而角色又賦予用戶,所以該部門有多大的權力,部門下的員工就有什么樣的權力··· 總體而言,RBAC針對公司內部項目,后臺管理開發居多
-
Django的內置RBAC(六表)
- 圖解
- 權限三表
- 權限六表
- 表模型
- 圖解
-
表關系
django的admin自帶rbac權限管理(表設計完成權限管理),6張表
- 用戶表、組表(角色、部門)、權限表 ---> 三張
- 用戶和組多對多關系,存在中間表
- 用戶和權限多對多關系,存在中間表
- 組和權限多對多關系,存在中間表 -----> 三張
-
實操
- models.pyfrom django.db import modelsclass Book(models.Model):name = models.CharField(max_length=32)price = models.DecimalField(max_digits=5, decimal_places=2)author = models.CharField(max_length=32)# 對象描述,顯示書名def __str__(self):return self.name# 表名中文解釋class Meta:'''verbose_name 顧名思義 起一個復雜點的名稱,一般用來作中文解釋verbose_name_plural 顧名思義是一個復數名稱,因中文沒有復數但django有時又會將用戶的駝峰命名拆成單個詞,給最后的詞加復數,和用戶的本義不符,因些加了這樣一個選項來處理尷尬 比如 Blog Articals 或是 分類管理s'''# verbose_name = '圖書表'verbose_name_plural = '圖書表'
- adminfrom django.contrib import admin from .models import Bookadmin.site.register(Book)
-
登錄admin操作
普通用戶只能查看
添加到組里,增加修改權限#
-
admin二次開發
初始樣式
- admin.pyfrom django.contrib import admin from .models import Bookclass BookAdmin(admin.ModelAdmin):# 設置列表可顯示的字段list_display = ('name', 'price', 'author')# 設置過濾選項list_filter = ('name', 'price')admin.site.register(Book, BookAdmin)
二次開發后的樣式
base64編碼與解碼
注意:base64長度必須是4的倍速,如果不夠就使用=補齊,
import base64 import jsondic_info={"sub": "1234567890","name": "HammerZe","admin": True }'''base64編碼''' # 必須是bytes類型 s = json.dumps(dic_info).encode('utf8') enbase64_str = base64.b64encode(s) print(enbase64_str) # b'eyJzdWIiOiAiMTIzNDU2Nzg5MCIsICJuYW1lIjogImxxeiIsICJhZG1pbiI6IHRydWV9''''base64解碼''' en_res = b'eyJzdWIiOiAiMTIzNDU2Nzg5MCIsICJuYW1lIjogImxxeiIsICJhZG1pbiI6IHRydWV9'debase64_str = base64.b64decode(en_res).decode('utf8') print(base64.b64decode(en_res),type(base64.b64decode(en_res))) # b'{"sub": "1234567890", "name": "HammerZe", "admin": true}' <class 'bytes'> print(debase64_str) # {"sub": "1234567890", "name": "HammerZe", "admin": true}JWT認證
Json web token (JWT), 是為了在網絡應用環境間傳遞聲明而執行的一種基于JSON的開放標準((RFC 7519).該token被設計為緊湊且安全的,特別適用于分布式站點的單點登錄(SSO)場景。JWT的聲明一般被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便于從資源服務器獲取資源,也可以增加一些額外的其它業務邏輯所必須的聲明信息,該token也可直接被用于認證,也可被加密。Json web token (JWT),token是一種認證機制,用在web開發方向,叫JWT
-
為什么使用JWT 認證?
如果登錄用戶很多,需要在后端存很多數據,頻繁查詢數據庫,導致效率低,JWT就可以使我們可以不在服務端存數據,又夠保證數據安全,在客戶端存數據 ----> token認證機制
-
Session機制
-
JWT機制
-
-
JWT的構成
JWT由3部分組成:標頭(Header)、有效載荷(Payload)和簽名(Signature)。在傳輸的時候,會將JWT的3部分分別進行Base64編碼后用.進行連接形成最終傳輸的字符串。比如:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ-
header
JWT頭是一個描述JWT元數據的JSON對象,alg屬性表示簽名使用的算法,默認為HMAC SHA256(寫為HS256);typ屬性表示令牌的類型,JWT令牌統一寫為JWT。最后,使用Base64 URL算法將上述JSON對象轉換為字符串保存
第一段頭部承載的信息:
- 聲明類型,這里是jwt
- 聲明加密的算法 通常直接使用 HMAC SHA256
完整的頭部就像下面這樣的JSON:
{"typ": "JWT","alg": "HS256" }然后將頭部進行base64加密(該加密是可以對稱解密的),構成了第一部分
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 -
payload
第二段是載荷,載荷就是存放有效信息的地方,是JWT的主體內容部分,也是一個JSON對象,承載的信息:
- 標準中注冊的聲明
- 公共的聲明
- 私有的聲明
標準中注冊的聲明 (建議但不強制使用) :
- iss: jwt簽發者
- sub: jwt所面向的用戶
- aud: 接收jwt的一方
- exp: jwt的過期時間,這個過期時間必須要大于簽發時間
- nbf: 定義在什么時間之前,該jwt都是不可用的.
- iat: jwt的簽發時間
- jti: jwt的唯一身份標識,主要用來作為一次性token,從而回避時序攻擊。
公共的聲明
公共的聲明可以添加任何的信息,一般添加用戶的相關信息或其他業務需要的必要信息,但不建議添加敏感信息,因為該部分在客戶端可解密.
私有的聲明
私有聲明是提供者和消費者所共同定義的聲明,一般不建議存放敏感信息,因為base64是對稱解密的,意味著該部分信息可以歸類為明文信息。
除以上標準注冊聲明字段外,我們還可以自定義字段,一般會把包含用戶信息的數據放到payload中,如下例:
{"sub": "1234567890","name": "HammerZe","admin": true }注意
雖然說用戶信息數據可以存放到payload中,但是默認情況下JWT是未加密的,Base64算法也只是編碼并不會提供安全的加密算法,一般程序員拿到Base64編碼的字符串都可以解碼出內容,所以不要存隱私信息,比如密碼,防止泄露,存一些非敏感信息
-
signature
簽名哈希部分是對上面兩部分數據簽名,需要使用base64編碼后的header和payload數據,通過指定的算法生成哈希,以確保數據不會被篡改。首先,需要指定一個密鑰(secret)。該密碼僅僅為保存在服務器中,并且不能向用戶公開。然后,使用header中指定的簽名算法(默認情況下為HMAC SHA256)根據以下公式生成簽名
HMACSHA256(base64UrlEncode(header) + “.” + base64UrlEncode(payload), secret)簡單的說第三段是簽證信息,這個簽證信息由三部分組成:
- header (base64后的)
- payload (base64后的)
- secret
這個部分需要base64加密后的header和base64加密后的payload使用.連接組成的字符串,然后通過header中聲明的加密方式進行加鹽secret組合加密,然后就構成了JWT的第三部分。
注意
secret是保存在服務器端的,jwt的簽發生成也是在服務器端的,secret就是用來進行jwt的簽發和jwt的驗證,所以,它就是你服務端的私鑰,在任何場景都不應該流露出去。一旦客戶端得知這個secret, 那就意味著客戶端是可以自我簽發jwt了。
關于簽發和核驗JWT,我們可以使用Django REST framework JWT擴展來完成。
總結
注意JWT每部分的作用,在服務端接收到客戶端發送過來的JWT token之后:header和payload可以直接利用base64解碼出原文,從header中獲取哈希簽名的算法,從payload中獲取有效數據signature由于使用了不可逆的加密算法,無法解碼出原文,它的作用是校驗token有沒有被篡改。服務端獲取header中的加密算法之后,利用該算法加上secretKey對header、payload進行加密,比對加密后的數據和客戶端發送過來的是否一致。注意secretKey只能保存在服務端,而且對于不同的加密算法其含義有所不同,一般對于MD5類型的摘要加密算法,secretKey實際上代表的是鹽值
-
-
本質原理
-
JWT認證算法:簽發與校驗
""" 1)jwt分三段式:頭.體.簽名 (head.payload.sgin) 2)頭和體是可逆加密,讓服務器可以反解出user對象;簽名是不可逆加密,保證整個token的安全性的 3)頭體簽名三部分,都是采用json格式的字符串,進行加密,可逆加密一般采用base64算法,不可逆加密一般采用hash(md5)算法 4)頭中的內容是基本信息:公司信息、項目組信息、token采用的加密方式信息 {"company": "公司信息",... } 5)體中的內容是關鍵信息:用戶主鍵、用戶名、簽發時客戶端信息(設備號、地址)、過期時間 {"user_id": 1,... } 6)簽名中的內容時安全信息:頭的加密結果 + 體的加密結果 + 服務器不對外公開的安全碼 進行md5加密 {"head": "頭的加密字符串","payload": "體的加密字符串","secret_key": "安全碼" } """ -
簽發:根據登錄請求提交來的 賬號 + 密碼 + 設備信息 簽發 token
""" 1)用基本信息存儲json字典,采用base64算法加密得到 頭字符串 2)用關鍵信息存儲json字典,采用base64算法加密得到 體字符串 3)用頭、體加密字符串再加安全碼信息存儲json字典,采用hash md5算法加密得到 簽名字符串賬號密碼就能根據User表得到user對象,形成的三段字符串用 . 拼接成token返回給前臺 """ -
校驗:根據客戶端帶token的請求 反解出 user 對象
""" 1)將token按 . 拆分為三段字符串,第一段 頭加密字符串 一般不需要做任何處理 2)第二段 體加密字符串,要反解出用戶主鍵,通過主鍵從User表中就能得到登錄用戶,過期時間和設備信息都是安全信息,確保token沒過期,且時同一設備來的 3)再用 第一段 + 第二段 + 服務器安全碼 不可逆md5加密,與第三段 簽名字符串 進行碰撞校驗,通過后才能代表第二段校驗得到的user對象就是合法的登錄用戶 """
-
-
JWT的種類
其實JWT(JSON Web Token)指的是一種規范,這種規范允許我們使用JWT在兩個組織之間傳遞安全可靠的信息,JWT的具體實現可以分為以下幾種:
-
nonsecure JWT
未經過簽名,不安全的JWT。其header部分沒有指定簽名算法,并且也沒有Signature部分
{"alg": "none","typ": "JWT" } -
JWS
JWS ,也就是JWT Signature,其結構就是在之前nonsecure JWT的基礎上,在頭部聲明簽名算法,并在最后添加上簽名。創建簽名,是保證jwt不能被他人隨意篡改。我們通常使用的JWT一般都是JWS
-
為了完成簽名,除了用到header信息和payload信息外,還需要算法的密鑰,也就是secretKey。加密的算法一般有2類:
- 對稱加密:secretKey指加密密鑰,可以生成簽名與驗簽
- 非對稱加密:secretKey指私鑰,只用來生成簽名,不能用來驗簽(驗簽用的是公鑰)
- JWT的密鑰或者密鑰對,一般統一稱為JSON Web Key,也就是JWK
-
到目前為止,jwt的簽名算法有三種:
- HMAC【哈希消息驗證碼(對稱)】:HS256/HS384/HS512
- RSASSA【RSA簽名算法(非對稱)】(RS256/RS384/RS512)
- ECDSA【橢圓曲線數據簽名算法(非對稱)】(ES256/ES384/ES512)
-
-
JWE
payload部分經過加密的JWT
-
django中快速使用JWT
- 簽發
一般我們登錄成功后簽發一個token串,token串分為三段,頭部,載荷,簽名
1)用基本信息公司信息存儲json字典,采用base64算法得到 頭字符串 2)用關鍵信息存儲json字典,采用base64算法得到 荷載字符串,過期時間,用戶id,用戶名 3)用頭、體加密字符串通過加密算法+秘鑰加密得到 簽名字符串 拼接成token返回給前臺 - 認證
根據客戶端帶token的請求 反解出 user 對象
1)將token按 . 拆分為三段字符串,第一段 頭部加密字符串 一般不需要做任何處理 2)第二段 體加密字符串,要反解出用戶主鍵,通過主鍵從User表中就能得到登錄用戶,過期時間是安全信息,確保token沒過期 3)再用 第一段 + 第二段 + 加密方式和秘鑰得到一個加密串,與第三段 簽名字符串 進行比較,通過后才能代表第二段校驗得到的user對象就是合法的登錄用戶 - JWT可以使用如下兩種:
djangorestframework-jwt和djangorestframework-simplejwt
- djangorestframework-jwt:https://github.com/jpadilla/django-rest-framework-jwt
- djangorestframework-simplejwt:https://github.com/jazzband/djangorestframework-simplejwt
- 區別:https://blog.csdn.net/lady_killer9/article/details/103075076
- 官網文檔:https://jpadilla.github.io/django-rest-framework-jwt/
-
django中快速使用JWT
導入:pip3 install djangorestframework-jwt
-
如何簽發?
- 路由中配置from rest_framework_jwt.views import obtain_jwt_token urlpatterns = [path('login/', obtain_jwt_token), ]
- 使用接口測試工具發送post請求到后端,就能基于auth的user表簽發token{"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6IkhhbW1lciIsImV4cCI6MTY0OTUyNDY2MiwiZW1haWwiOiIifQ.P1Y8Z3WhdndHoWE0PjW-ygd53Ng0T46U04oY8_0StwI" }
- base64反解import base64# 第一段 s1 = b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9' print(base64.b64decode(s1)) # b'{"typ":"JWT","alg":"HS256"}'# 第二段 s2 = b'eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6IkhhbW1lciIsImV4cCI6MTY0OTUyNDY2MiwiZW1haWwiOiIifQ==' print(base64.b64decode(s2)) # b'{"user_id":1,"username":"Hammer","exp":1649524662,"email":""}' # 我們發現第二段可以反解密出用戶信息,是有一定的風險,可以使用,但是不能更改,就好比你的身份證丟了,別人可以在你不掛失的情況下去網吧上網'''第三段不能不能反解,只能做base64解碼,第三段使用base64編碼只是為了統一格式'''
-
如何認證?
我們沒有認證的時候,直接訪問接口就可以返回數據,比如訪問/books/發送GET請求就可以獲取所有book信息,那么現在添加認證,需要訪問通過才能訪問才更合理
- 步驟:
- 視圖中配置,必須配置認證類和權限類
- 訪問需要在請求頭中使用,攜帶簽發的token串,格式是:key是Authorization value是jwt token串 Authorization : jwt token串 '''注意jwt和token串中間有空格'''
- 視圖from rest_framework_jwt.authentication import JSONWebTokenAuthentication from rest_framework.permissions import IsAuthenticated class BookView(GenericViewSet,ListModelMixin):···# JSONWebTokenAuthentication :rest_framework_jwt模塊寫的認證類authentication_classes = [JSONWebTokenAuthentication,]# 需要配合一個權限類permission_classes = [IsAuthenticated,]···
- 步驟:
-
定制簽發token返回格式
JWT默認的配置是,我們登錄成功后只返回一個token串,這也是默認的配置,我們如果想簽發token后返回更多數據需要我們自定制
- 步驟
- 寫一個函數,返回什么格式,前端就能看見什么格式
- 在配置文件中配置JWT_AUTH
- utils.py# 定義簽發token(登陸接口)返回格式 def jwt_response_payload_handler(token, user=None, request=None):return {'code': 100,'msg': "登陸成功",'token': token,'username': user.username}
- settings.pyJWT_AUTH = {'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.utils.jwt_response_payload_handler',}
- 步驟
-
-
JWT源碼分析
-
簽發源碼分析
1.入口:path('login/', obtain_jwt_token)2.obtain_jwt_token--->obtain_jwt_token = ObtainJSONWebToken.as_view() ObtainJSONWebToken.as_view(),其實就是一個視圖類.as_view()3.ObtainJSONWebToken類源碼 ''' class ObtainJSONWebToken(JSONWebTokenAPIView):serializer_class = JSONWebTokenSerializer '''4.登錄簽發token肯定需要一個post方法出來,但是ObtainJSONWebToken類內沒有父類JSONWebTokenAPIView寫了post方法:def post(self, request, *args, **kwargs):# 獲取數據:{'username': 'Hammer', 'password': '7410'}serializer = self.get_serializer(data=request.data)# 校驗if serializer.is_valid():user = serializer.object.get('user') or request.user # 獲取用戶token = serializer.object.get('token') # 獲取tokenresponse_data = jwt_response_payload_handler(token, user, request) # {'code': 100, 'msg': '登陸成功', 'token': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6IkhhbW1lciIsImV4cCI6MTY0OTU4MTU0NiwiZW1haWwiOiIifQ.2oAjKQ90SV2S9Yxrwppo7BwAOv0xFW4i4AHHBX5Cg2Q', 'username': 'Hammer'}response = Response(response_data)if api_settings.JWT_AUTH_COOKIE:···return response # 定制什么返回什么return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)5.get_serializer(data=request.data)如何獲取到用戶數據? JSONWebTokenSerializer序列化類中全局鉤子中獲取當前登錄用戶和簽發token ··· payload = jwt_payload_handler(user)return {'token': jwt_encode_handler(payload),'user': user} ···簽發總結
從obtain_jwt_token開始, 通過ObtainJSONWebToken視圖類處理,其實是父類JSONWebTokenAPIView的post方法通過傳入的用戶名和密碼處理獲取當前用戶,簽發了token
-
認證源碼分析
# 視圖類內認證類搭配權限類使用authentication_classes = [JSONWebTokenAuthentication, ]permission_classes = [IsAuthenticated, ]我們在前面寫過,如果需要認證肯定需要重寫authenticate方法,這里從列表內的認證類作為入口分析:
'''認證類源碼''' class JSONWebTokenAuthentication(BaseJSONWebTokenAuthentication):www_authenticate_realm = 'api'def get_jwt_value(self, request):# 獲取傳入的Authorization:jwt token串,然后切分auth = get_authorization_header(request).split()auth_header_prefix = api_settings.JWT_AUTH_HEADER_PREFIX.lower()# 獲取不到的情況if not auth:if api_settings.JWT_AUTH_COOKIE:return request.COOKIES.get(api_settings.JWT_AUTH_COOKIE)return None # 直接返回None,也不會報錯,所以必須搭配權限類使用···return auth[1] # 一切符合判斷條件,通過split切分的列表索引到token串 '''認證類父類源碼''' def authenticate(self, request):jwt_value = self.get_jwt_value(request) # 獲取真正的token,三段式,上面分析if jwt_value is None: # 如果沒傳token,就不認證了,直接通過,所以需要配合權限類一起用return Nonetry:payload = jwt_decode_handler(jwt_value)# 驗證簽名except jwt.ExpiredSignature:msg = _('Signature has expired.') # 過期了raise exceptions.AuthenticationFailed(msg)except jwt.DecodeError:msg = _('Error decoding signature.')# 被篡改了raise exceptions.AuthenticationFailed(msg)except jwt.InvalidTokenError:raise exceptions.AuthenticationFailed()# 不知名的錯誤user = self.authenticate_credentials(payload)return (user, jwt_value) -
簽發源碼內的其他兩個類
導入:from rest_framework_jwt.views import obtain_jwt_token,refresh_jwt_token,verify_jwt_token
obtain_jwt_token = ObtainJSONWebToken.as_view() # 獲取token refresh_jwt_token = RefreshJSONWebToken.as_view() # 更新token verify_jwt_token = VerifyJSONWebToken.as_view() # 認證tokenrefresh_jwt_token用法
# 配置文件 JWT_AUTH = {'JWT_ALLOW_REFRESH': True } # 路由path('refresh/', refresh_jwt_token)verify_jwt_token用法
path('verify/', verify_jwt_token),
-
自定義User表,簽發token
-
普通寫法,視圖類寫
上面我們寫道,簽發token是基于Django自帶的auth_user表簽發,如果我們自定義User表該如何簽發token,如下:
- 視圖# 自定義表簽發token from rest_framework.views import APIView from rest_framework.viewsets import ViewSetMixin from rest_framework.decorators import action from rest_framework.response import Response from rest_framework_jwt.settings import api_settings from app01 import models class UserView(ViewSetMixin,APIView):@action(methods=['POST'],detail=False)def login(self,request):username = request.data.get('username')password = request.data.get('password')user = models.UserInfo.objects.filter(username=username,password=password).first()response_dict = {'code':None,'msg':None}# 源碼copy錯來使用jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLERjwt_encode_handler = api_settings.JWT_ENCODE_HANDLERif user:'''簽發token去源碼copy過來使用'''# 載荷字典payload = jwt_payload_handler(user)print(payload)# {'user_id': 1, 'username': 'Hammer', 'exp': datetime.datetime(2022, 4, 10, 13, 13, 15, 363206), 'email': '123@qq.com', 'orig_iat': 1649596095}# 通過荷載得到token串token = jwt_encode_handler(payload)response_dict['code'] = 2000response_dict['msg'] = '登錄成功'response_dict['token'] = tokenelse:response_dict['code'] = 4001response_dict['msg'] = '登錄失敗,用戶名或密碼錯誤'return Response(response_dict)
- 模型# user表 class UserInfo(models.Model):username = models.CharField(max_length=32)password = models.CharField(max_length=32)email = models.EmailField()
- 路由from rest_framework.routers import SimpleRouter router = SimpleRouter() router.register('user',views.UserView,'user')
-
序列化類中寫邏輯
源碼中簽發校驗都在序列化類中完成,這種寫法確實比較常用,我們來使用這種方式自定義,將上面視圖的校驗邏輯寫到序列化類中,這個序列化類只用來做反序列化,這樣我們就可以利用 反序列化 的字段校驗功能來幫助我們校驗(模型中的條件),但是我們不做保存操作
- 視圖from .serializer import UserInfoSerializer class UserView(ViewSetMixin,APIView):@action(methods=['POST'],detail=False)def login(self,request):# 如果想獲取什么這里可以實例化對象寫入,比如requestserializer = UserInfoSerializer(data=request.data, context={'request': request})response_dict = {'code':None,'msg':None}# 校驗,局部鉤子,全局鉤子都校驗完才算校驗通過,走自己的校驗規則if serializer.is_valid():# 從序列化器對象中獲取token和usernametoken = serializer.context.get('token')username = serializer.context.get('username')response_dict['code']=2000response_dict['msg']='登錄成功'response_dict['token'] = tokenresponse_dict['username'] = usernameelse:response_dict['code'] = 4001response_dict['msg'] = '登錄失敗,用戶名或密碼錯誤'return Response(response_dict)
- 序列化器from rest_framework.exceptions import ValidationErrorclass UserInfoSerializer(serializers.ModelSerializer):class Meta:model = UserInfo# 根據模型里的字段寫fields = ['username', 'password']# 全局鉤子def validate(self, attrs):# attrs是校驗過的字段,這里利用username = attrs.get('username')password = attrs.get('password')user = UserInfo.objects.filter(username=username, password=password).first()from rest_framework_jwt.settings import api_settingsjwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLERjwt_encode_handler = api_settings.JWT_ENCODE_HANDLERif user: # 登錄成功payload = jwt_payload_handler(user) # 得到荷載字典token = jwt_encode_handler(payload) # 通過荷載得到token串# 將token放入context字典中self.context['token'] = tokenself.context['username'] = username# context是serializer和視圖類溝通的橋梁print(self.context.get('request').method)else: # 登錄失敗raise ValidationError('用戶名或密碼錯誤')return attrs
- 總結
需要我們注意的是,context只是我們定義的字典,比如上面寫到的實例化序列化類中指定的context,那么就可以從序列化類打印出請求的方法,context是序列化類和視圖類溝通的橋梁
-
自定義認證類
-
auth.py
import jwt from django.utils.translation import ugettext as _ from rest_framework import exceptions from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed from rest_framework_jwt.settings import api_settings from .models import UserInfoclass JWTAuthentication(BaseAuthentication):def authenticate(self, request):# 第一步、取出傳入的token,從請求頭中取# 這里注意,獲取的時候格式為:HTTP_請求頭的key大寫jwt_value = request.META.get('HTTP_TOKEN')jwt_decode_handler = api_settings.JWT_DECODE_HANDLER# 驗證token:驗證是否過期,是否被篡改,是否有其他未知錯誤,從源碼copy過來使用if jwt_value:try:payload = jwt_decode_handler(jwt_value)except jwt.ExpiredSignature:msg = _('Signature has expired.')raise exceptions.AuthenticationFailed(msg)except jwt.DecodeError:msg = _('Error decoding signature.')raise exceptions.AuthenticationFailed(msg)except jwt.InvalidTokenError:msg = _('Unknown Error.')raise exceptions.AuthenticationFailed(msg)# 第二部、通過payload獲得當前登錄用戶,本質是用戶信息通過base64編碼到token串的第二段載荷中user = UserInfo.objects.filter(pk=payload['user_id']).first()# 返回user和tokenreturn (user, jwt_value)else:raise AuthenticationFailed('No token was detected') -
視圖
from rest_framework.viewsets import ModelViewSet from .models import Book from .serializer import BookSerializer from .auth import JWTAuthentication class BookView(ModelViewSet):queryset = Book.objects.all()serializer_class = BookSerializerauthentication_classes = [JWTAuthentication,] -
序列化器
class BookSerializer(serializers.ModelSerializer):class Meta:model = Bookfields = '__all__' -
路由
from rest_framework.routers import SimpleRouter router = SimpleRouter() router.register('book',views.BookView,'book')正常的情況
不攜帶token的情況
總結
- 從請求頭中獲取token,格式是HTTP_KEY,key要大寫
- 認證token串沒有問題,返回用戶信息從載荷中獲取,本質是用戶信息通過base64編碼到token串的第二段載荷中,可以通過base64解碼獲取到用戶信息
-
-
補充:HttpRequest.META
HTTP請求的數據在META中
HttpRequest.META 一個標準的Python 字典,包含所有的HTTP 首部。具體的頭部信息取決于客戶端和服務器,下面是一些示例: : CONTENT_LENGTH —— 請求的正文的長度(是一個字符串)。 CONTENT_TYPE —— 請求的正文的MIME 類型。 HTTP_ACCEPT —— 響應可接收的Content-Type。 HTTP_ACCEPT_ENCODING —— 響應可接收的編碼。 HTTP_ACCEPT_LANGUAGE —— 響應可接收的語言。 HTTP_HOST —— 客服端發送的HTTP Host 頭部。 HTTP_REFERER —— Referring 頁面。 HTTP_USER_AGENT —— 客戶端的user-agent 字符串。 QUERY_STRING —— 單個字符串形式的查詢字符串(未解析過的形式)。 REMOTE_ADDR —— 客戶端的IP 地址。 REMOTE_HOST —— 客戶端的主機名。 REMOTE_USER —— 服務器認證后的用戶。 REQUEST_METHOD —— 一個字符串,例如"GET" 或"POST"。 SERVER_NAME —— 服務器的主機名。 SERVER_PORT —— 服務器的端口(是一個字符串)。 上面可以看到,除 CONTENT_LENGTH 和 CONTENT_TYPE 之外,請求中的任何 HTTP 首部轉換為 META 的鍵時, 都會將所有字母大寫并將連接符替換為下劃線最后加上 HTTP_ 前綴。 所以,一個叫做 X-Bender 的頭部將轉換成 META 中的 HTTP_X_BENDER 鍵。
Simple UI快速上手
在混合開發的模式下,如果想使用django admin,又嫌棄后臺不符合你的審美?Simple UI給你想要的答案
Simple UI官方文檔:傳送門
-
支持django和python版本
- 支持的Python版本 >= 2.7
- 支持的Django版本 >= 2.x
-
安裝
pip3 install django-simpleui
-
注冊
在自己項目的settings.py文件中INSTALLED_APPS的第一行加入simpleui
# Application definitionINSTALLED_APPS = ['simpleui','django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles',...] -
模型
from django.db import models# Create your models here.class Book(models.Model):name = models.CharField(max_length=32,help_text='書名')price = models.DecimalField(max_digits=5, decimal_places=2,help_text='價錢')author = models.CharField(max_length=32,help_text='作者')# 對象描述,顯示書名def __str__(self):return self.name# 表名中文解釋class Meta:'''verbose_name 顧名思義 起一個復雜點的名稱,一般用來作中文解釋verbose_name_plural 顧名思義是一個復數名稱,因中文沒有復數但django有時又會將用戶的駝峰命名拆成單個詞,給最后的詞加復數,和用戶的本義不符,因些加了這樣一個選項來處理尷尬 比如 Blog Articals 或是 分類管理s'''# verbose_name = '圖書表'verbose_name_plural = '圖書表' -
admin
from django.contrib import adminfrom .models import Book# Register your models here.class BookAdmin(admin.ModelAdmin):# 設置列表可顯示的字段list_display = ('name', 'price', 'author')# 設置過濾選項list_filter = ('name', 'price')admin.site.register(Book, BookAdmin) -
測試
-
自定義菜單
import time SIMPLEUI_CONFIG = {'system_keep': False,'menu_display': ['Simpleui', '測試', '權限認證', '動態菜單測試'], # 開啟排序和過濾功能, 不填此字段為默認排序和全部顯示, 空列表[] 為全部不顯示.'dynamic': True, # 設置是否開啟動態菜單, 默認為False. 如果開啟, 則會在每次用戶登陸時動態展示菜單內容'menus': [{'name': 'Simpleui','icon': 'fas fa-code','url': 'https://gitee.com/tompeppa/simpleui'}, {'app': 'auth','name': '權限認證','icon': 'fas fa-user-shield','models': [{'name': '用戶','icon': 'fa fa-user','url': 'auth/user/'}]}, {# 自2021.02.01+ 支持多級菜單,models 為子菜單名'name': '多級菜單測試','icon': 'fa fa-file',# 二級菜單'models': [{'name': 'Baidu','icon': 'far fa-surprise',# 第三級菜單 ,'models': [{'name': '愛奇藝','url': 'https://www.iqiyi.com/dianshiju/'# 第四級就不支持了,element只支持了3級}, {'name': '百度問答','icon': 'far fa-surprise','url': 'https://zhidao.baidu.com/'}]}, {'name': '內網穿透','url': 'https://www.wezoz.com','icon': 'fab fa-github'}]}, {'name': '動態菜單測試' ,'icon': 'fa fa-desktop','models': [{'name': time.time(),'url': 'http://baidu.com','icon': 'far fa-surprise'}]}] } -
快速引用靜態文件配置
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static'), ]
總結
以上是生活随笔為你收集整理的DRF访问控制(RBAC)、JWT认证的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Word添加题注、域的使用
- 下一篇: 【vue3组件封装】Icon图标组件