django drf框架中的user驗(yàn)證以及JWT拓展的介紹
登錄注冊(cè)是幾乎所有網(wǎng)站都需要去做的接口,而說(shuō)到登錄,自然也就涉及到驗(yàn)證以及用戶登錄狀態(tài)保存,最近用DRF在做的一個(gè)關(guān)于網(wǎng)上商城的項(xiàng)目中,引入了一個(gè)拓展DRF JWT,專(zhuān)門(mén)用于做驗(yàn)證和用戶狀態(tài)保存。這個(gè)拓展比傳統(tǒng)的CSRF更加安全。先來(lái)介紹一下JWT認(rèn)證機(jī)制吧!
Json web token (JWT), 是為了在網(wǎng)絡(luò)應(yīng)用環(huán)境間傳遞聲明而執(zhí)行的一種基于JSON的開(kāi)放標(biāo)準(zhǔn)( (RFC 7519 ).該token被設(shè)計(jì)為緊湊且安全的,特別適用于分布式站點(diǎn)的單點(diǎn)登錄(SSO)場(chǎng)景。JWT的聲明一般被用來(lái)在身份提供者和服務(wù)提供者間傳遞被認(rèn)證的用戶身份信息,以便于從資源服務(wù)器獲取資源,也可以增加一些額外的其它業(yè)務(wù)邏輯所必須的聲明信息,該token也可直接被用于認(rèn)證,也可被加密。
基于token的鑒權(quán)機(jī)制
基于token的鑒權(quán)機(jī)制類(lèi)似于http協(xié)議也是無(wú)狀態(tài)的,它不需要在服務(wù)端去保留用戶的認(rèn)證信息或者會(huì)話信息。這就意味著基于token認(rèn)證機(jī)制的應(yīng)用不需要去考慮用戶在哪一臺(tái)服務(wù)器登錄了,這就為應(yīng)用的擴(kuò)展提供了便利。
流程上是這樣的:
- 用戶使用用戶名密碼來(lái)請(qǐng)求服務(wù)器
- 服務(wù)器進(jìn)行驗(yàn)證用戶的信息
- 服務(wù)器通過(guò)驗(yàn)證發(fā)送給用戶一個(gè)token
- 客戶端存儲(chǔ)token,并在每次請(qǐng)求時(shí)附送上這個(gè)token值
- 服務(wù)端驗(yàn)證token值,并返回?cái)?shù)據(jù)
這個(gè)token必須要在每次請(qǐng)求時(shí)傳遞給服務(wù)端,它應(yīng)該保存在請(qǐng)求頭里, 另外,服務(wù)端要支持 CORS(跨來(lái)源資源共享) 策略,一般我們?cè)诜?wù)端這么做就可以了 Access-Control-Allow-Origin: * 。
那么我們現(xiàn)在回到JWT的主題上。
JWT的構(gòu)成
第一部分我們稱(chēng)它為頭部(header),第二部分我們稱(chēng)其為載荷(payload, 類(lèi)似于飛機(jī)上承載的物品),第三部分是簽證(signature).
jwt的頭部承載兩部分信息:1,聲明類(lèi)型,這里是jwt,2聲明加密的算法 通常直接使用 HMAC SHA256。完整的頭部就像下面這樣的JSON:
{
'typ': 'JWT',
'alg': 'HS256'
}
然后將頭部進(jìn)行base64加密(該加密是可以對(duì)稱(chēng)解密的),構(gòu)成了第一部分。
如 eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9。
載荷就是存放有效信息的地方。這個(gè)名字像是特指飛機(jī)上承載的貨品,這些有效信息包含三個(gè)部分1標(biāo)準(zhǔn)中注冊(cè)的聲明,2公共的聲明,3私有的聲明。標(biāo)準(zhǔn)中注冊(cè)的聲明 (建議但不強(qiáng)制使用) :1 iss: jwt簽發(fā)者,2 sub: jwt所面向的用戶,3 aud: 接收jwt的一方,4 exp: jwt的過(guò)期時(shí)間,這個(gè)過(guò)期時(shí)間必須要大于簽發(fā)時(shí)間,5 nbf: 定義在什么時(shí)間之前,該jwt都是不可用的,6 iat: jwt的簽發(fā)時(shí)間,7 jti: jwt的唯一身份標(biāo)識(shí),主要用來(lái)作為一次性token,從而回避重放攻擊。公共的聲明 : 公共的聲明可以添加任何的信息,一般添加用戶的相關(guān)信息或其他業(yè)務(wù)需要的必要信息.但不建議添加敏感信息,因?yàn)樵摬糠衷诳蛻舳丝山饷?私有的聲明 : 私有聲明是提供者和消費(fèi)者所共同定義的聲明,一般不建議存放敏感信息,因?yàn)閎ase64是對(duì)稱(chēng)解密的,意味著該部分信息可以歸類(lèi)為明文信息。定義一個(gè)payload:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
然后將其進(jìn)行base64加密,得到JWT的第二部分。
如 eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9。
JWT的第三部分是一個(gè)簽證信息,這個(gè)簽證信息由三部分組成:1 header (base64后的),2 payload (base64后的),3 secret。這個(gè)部分需要base64加密后的header和base64加密后的payload使用 . 連接組成的字符串,然后通過(guò)header中聲明的加密方式進(jìn)行加鹽 secret 組合加密,然后就構(gòu)成了jwt的第三部分。
注意:secret是保存在服務(wù)器端的,jwt的簽發(fā)生成也是在服務(wù)器端的,secret就是用來(lái)進(jìn)行jwt的簽發(fā)和jwt的驗(yàn)證,所以,它就是你服務(wù)端的私鑰,在任何場(chǎng)景都不應(yīng)該流露出去。一旦客戶端得知這個(gè)secret, 那就意味著客戶端是可以自我簽發(fā)jwt了。
首先需要安裝拓展 pip install djangorestframework-jwt,然后在django進(jìn)行配置, JWT_EXPIRATION_DELTA 指明token的有效期。
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
),
}
JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
}
Django REST framework JWT 擴(kuò)展的說(shuō)明文檔中提供了手動(dòng)簽發(fā)JWT的方法
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)
在注冊(cè)時(shí),引入上述代碼,簽發(fā)JWT即可。而對(duì)于登錄,JWT拓展提供了內(nèi)置的視圖,在urls中添加對(duì)于路由即可。
from rest_framework_jwt.views import obtain_jwt_token urlpatterns = [ url(r'^authorizations/$', obtain_jwt_token), ]
雖然寫(xiě)起來(lái)很簡(jiǎn)單,但是內(nèi)部其實(shí)做了很多的事,今天就來(lái)詳細(xì)研究一下,源代碼內(nèi)部做了哪些事情。
當(dāng)用戶登錄,會(huì)以post形式發(fā)請(qǐng)求到后端,會(huì)訪問(wèn) obtain_jwt_token中的post方法,在源代碼中可以看到 obtain_jwt_token = ObtainJSONWebToken.as_view(),跟我們寫(xiě)的類(lèi)視圖十分類(lèi)似,這是一個(gè)內(nèi)部已經(jīng)寫(xiě)好的類(lèi)視圖。
class ObtainJSONWebToken(JSONWebTokenAPIView): """ API View that receives a POST with a user's username and password. Returns a JSON Web Token that can be used for authenticated requests. """ serializer_class = JSONWebTokenSerializer
該類(lèi)并未定義任何方法,所以對(duì)應(yīng)的post方法應(yīng)該寫(xiě)在父類(lèi),下面是父類(lèi)中的post方法
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
user = serializer.object.get('user') or request.user
token = serializer.object.get('token')
response_data = jwt_response_payload_handler(token, user, request)
response = Response(response_data)
if api_settings.JWT_AUTH_COOKIE:
expiration = (datetime.utcnow() +
api_settings.JWT_EXPIRATION_DELTA)
response.set_cookie(api_settings.JWT_AUTH_COOKIE,
token,
expires=expiration,
httponly=True)
return response
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
上述方法返回一個(gè)Response對(duì)象,經(jīng)過(guò)一系列操作返回到前端,訪問(wèn)結(jié)束。
而在DRF框架中,在調(diào)用視圖之前,就會(huì)進(jìn)行相應(yīng)的驗(yàn)證操作。想要了解整個(gè)過(guò)程,需要我們從源代碼中一步步去探索。當(dāng)前端發(fā)起一個(gè)請(qǐng)求到后端,會(huì)根據(jù)路由訪問(wèn)對(duì)象的視圖類(lèi)的as_view()方法,該方法會(huì)接著調(diào)用dispatch()方法,APIView是DRF中所有視圖類(lèi)的父類(lèi),可以看一下他的dispatch方法。
def dispatch(self, request, *args, **kwargs):
"""
`.dispatch()` is pretty much the same as Django's regular dispatch,
but with extra hooks for startup, finalize, and exception handling.
"""
self.args = args
self.kwargs = kwargs
request = self.initialize_request(request, *args, **kwargs)
self.request = request
self.headers = self.default_response_headers # deprecate?
try:
self.initial(request, *args, **kwargs)
# Get the appropriate handler method
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(),
self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
response = handler(request, *args, **kwargs)
except Exception as exc:
response = self.handle_exception(exc)
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
可以看到,到請(qǐng)求進(jìn)來(lái),會(huì)調(diào)用self.initalize_request()方法對(duì)請(qǐng)求進(jìn)行處理。
def initialize_request(self, request, *args, **kwargs): """ Returns the initial request object. """ parser_context = self.get_parser_context(request) return Request( request, parsers=self.get_parsers(), authenticators=self.get_authenticators(), negotiator=self.get_content_negotiator(), parser_context=parser_context )
我們需要關(guān)注的只有 authenticators=self.get_authenticators()這一句,
def get_authenticators(self): """ Instantiates and returns the list of authenticators that this view can use. """ return [auth() for auth in self.authentication_classes]
接著往上照,可以看到類(lèi)屬性 permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES,這就是要什么我們要在django配置中加入DEFAULT_PERMISSION_CLASSES配置,上述方法會(huì)遍歷我們?cè)谂渲弥袑?xiě)到的列表,拿到里面的驗(yàn)證類(lèi),并進(jìn)行實(shí)例化,并將生成的對(duì)象裝在一個(gè)新的列表中,保存在新生成的Request對(duì)象中。然后我們接著看dispatch方法,在實(shí)際調(diào)用視圖類(lèi)中的對(duì)應(yīng)方法前,還調(diào)用了self.initial()方法進(jìn)行一些初始化操作。
def initial(self, request, *args, **kwargs): """ Runs anything that needs to occur prior to calling the method handler. """ self.format_kwarg = self.get_format_suffix(**kwargs) # Perform content negotiation and store the accepted info on the request neg = self.perform_content_negotiation(request) request.accepted_renderer, request.accepted_media_type = neg # Determine the API version, if versioning is in use. version, scheme = self.determine_version(request, *args, **kwargs) request.version, request.versioning_scheme = version, scheme # Ensure that the incoming request is permitted self.perform_authentication(request) self.check_permissions(request) self.check_throttles(request)
我們需要關(guān)注的是最后三排,分別調(diào)用了三個(gè)方法,進(jìn)行身份驗(yàn)證,權(quán)限驗(yàn)證和分流操作,這里我們只關(guān)注 身份驗(yàn)證方法,self.perform_authentication(),該方法內(nèi)只有一句代碼request.user,看起來(lái)像是在調(diào)用request對(duì)象的user屬性,其實(shí)不然,我們可以到DRF框架的Request對(duì)象中找到以下方法。
@property
def user(self):
"""
Returns the user associated with the current request, as authenticated
by the authentication classes provided to the request.
"""
if not hasattr(self, '_user'):
with wrap_attributeerrors():
self._authenticate()
return self._user
user方法經(jīng)過(guò)property裝飾器裝飾后,就可以像一個(gè)屬性一樣調(diào)用該方法,該方法在Request對(duì)象中存在對(duì)應(yīng)的user時(shí)會(huì)直接返回,若用戶登陸時(shí),Request對(duì)象中沒(méi)有對(duì)應(yīng)的user,所以代碼會(huì)走if判斷里面,我們只需要關(guān)注方法self._authenticate()的調(diào)用即可。
def _authenticate(self):
"""
Attempt to authenticate the request using each authentication instance
in turn.
"""
for authenticator in self.authenticators:
try:
user_auth_tuple = authenticator.authenticate(self)
except exceptions.APIException:
self._not_authenticated()
raise
if user_auth_tuple is not None:
self._authenticator = authenticator
self.user, self.auth = user_auth_tuple
return
self._not_authenticated()
可以看到,該方法會(huì)遍歷我們?cè)谥疤幚鞷equest對(duì)象時(shí)傳入的裝著驗(yàn)證類(lèi)對(duì)象的列表,并調(diào)用驗(yàn)證類(lèi)的authenticate()方法,若驗(yàn)證成功生成對(duì)應(yīng)的self.user和self.auth并直接return,往上則直接將生成的self.user進(jìn)行返回,若驗(yàn)證內(nèi)部出錯(cuò),會(huì)調(diào)用 self._not_ authenticated(), 并拋出錯(cuò)誤,往上看,在dispatch方法中,若初始化方法出錯(cuò),則進(jìn)行捕獲,并調(diào)用self.handle_exception()方法生成一個(gè)Response對(duì)象進(jìn)行返回,不會(huì)執(zhí)行視圖類(lèi)中對(duì)應(yīng)的方法,則調(diào)用對(duì)于的是self._not_authenticated()。
def _not_authenticated(self): """ Set authenticator, user & authtoken representing an unauthenticated request. Defaults are None, AnonymousUser & None. """ self._authenticator = None if api_settings.UNAUTHENTICATED_USER: self.user = api_settings.UNAUTHENTICATED_USER() else: self.user = None if api_settings.UNAUTHENTICATED_TOKEN: self.auth = api_settings.UNAUTHENTICATED_TOKEN() else: self.auth = None
UNAUTHENTICATED_USER在django默認(rèn)配置中為一個(gè)匿名用戶的類(lèi),UNAUTHENTICATED_TOKEN默認(rèn)為None,若所有驗(yàn)證都為通過(guò)或者某一驗(yàn)證過(guò)程中出錯(cuò),則生成一個(gè)匿名用戶,并將self.auth設(shè)置為None。
綜上所述,在請(qǐng)求執(zhí)行之前,DRF框架會(huì)根據(jù)我們?cè)谂渲梦募信渲玫尿?yàn)證類(lèi)對(duì)用戶進(jìn)行身份驗(yàn)證,若未通過(guò)驗(yàn)證,則會(huì)生成一個(gè)匿名用戶,驗(yàn)證通過(guò),則生成對(duì)應(yīng)的用戶。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
PyQt5 對(duì)圖片進(jìn)行縮放的實(shí)例
今天小編就為大家分享一篇PyQt5 對(duì)圖片進(jìn)行縮放的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-06-06
簡(jiǎn)單瞅瞅Python vars()內(nèi)置函數(shù)的實(shí)現(xiàn)
這篇文章主要介紹了簡(jiǎn)單瞅瞅Python vars()內(nèi)置函數(shù)的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09
用TensorFlow實(shí)現(xiàn)多類(lèi)支持向量機(jī)的示例代碼
這篇文章主要介紹了用TensorFlow實(shí)現(xiàn)多類(lèi)支持向量機(jī)的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-04-04
使用python的pandas讀取excel文件中的數(shù)據(jù)詳情
這篇文章主要介紹了使用python的pandas讀取excel文件中的數(shù)據(jù)詳情,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-09-09
Python3 SSH遠(yuǎn)程連接服務(wù)器的方法示例
這篇文章主要介紹了Python3 SSH遠(yuǎn)程連接服務(wù)器的方法示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-12-12
Pycharm如何導(dǎo)入python文件及解決報(bào)錯(cuò)問(wèn)題
這篇文章主要介紹了Pycharm如何導(dǎo)入python文件及解決報(bào)錯(cuò)問(wèn)題,本文通過(guò)示例截圖相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-05
python翻譯軟件實(shí)現(xiàn)代碼(使用google api完成)
這篇文章主要介紹了python結(jié)合google api完成的翻譯軟件實(shí)現(xiàn)代碼,大家參考使用2013-11-11

