django drf框架中的user驗證以及JWT拓展的介紹

登陸註冊是幾乎全部網站都須要去作的接口,而說到登陸,天然也就涉及到驗證以及用戶登陸狀態保存,最近用DRF在作的一個關於網上商城的項目中,引入了一個拓展DRF JWT,專門用於作驗證和用戶狀態保存。這個拓展比傳統的CSRF更加安全。先來介紹一下JWT認證機制吧!html

Json web token (JWT), 是爲了在網絡應用環境間傳遞聲明而執行的一種基於JSON的開放標準((RFC 7519).該token被設計爲緊湊且安全的,特別適用於分佈式站點的單點登陸(SSO)場景。JWT的聲明通常被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便於從資源服務器獲取資源,也能夠增長一些額外的其它業務邏輯所必須的聲明信息,該token也可直接被用於認證,也可被加密。前端

基於token的鑑權機制

基於token的鑑權機制相似於http協議也是無狀態的,它不須要在服務端去保留用戶的認證信息或者會話信息。這就意味着基於token認證機制的應用不須要去考慮用戶在哪一臺服務器登陸了,這就爲應用的擴展提供了便利。python

流程上是這樣的:web

  • 用戶使用用戶名密碼來請求服務器
  • 服務器進行驗證用戶的信息
  • 服務器經過驗證發送給用戶一個token
  • 客戶端存儲token,並在每次請求時附送上這個token值
  • 服務端驗證token值,並返回數據

這個token必需要在每次請求時傳遞給服務端,它應該保存在請求頭裏, 另外,服務端要支持CORS(跨來源資源共享)策略,通常咱們在服務端這麼作就能夠了Access-Control-Allow-Origin: *算法

那麼咱們如今回到JWT的主題上。shell

JWT的構成

第一部分咱們稱它爲頭部(header),第二部分咱們稱其爲載荷(payload, 相似於飛機上承載的物品),第三部分是簽證(signature).django

jwt的頭部承載兩部分信息:1,聲明類型,這裏是jwt,2聲明加密的算法 一般直接使用 HMAC SHA256。完整的頭部就像下面這樣的JSON:後端

{
  'typ': 'JWT',
  'alg': 'HS256'
}

而後將頭部進行base64加密(該加密是能夠對稱解密的),構成了第一部分。如eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9。api

載荷就是存放有效信息的地方。這個名字像是特指飛機上承載的貨品,這些有效信息包含三個部分1標準中註冊的聲明,2公共的聲明,3私有的聲明。標準中註冊的聲明 (建議但不強制使用) :1 iss: jwt簽發者,2 sub: jwt所面向的用戶,3 aud: 接收jwt的一方,4 exp: jwt的過時時間,這個過時時間必需要大於簽發時間,5 nbf: 定義在什麼時間以前,該jwt都是不可用的,6 iat: jwt的簽發時間,7 jti: jwt的惟一身份標識,主要用來做爲一次性token,從而回避重放攻擊。公共的聲明 : 公共的聲明能夠添加任何的信息,通常添加用戶的相關信息或其餘業務須要的必要信息.但不建議添加敏感信息,由於該部分在客戶端可解密.私有的聲明 : 私有聲明是提供者和消費者所共同定義的聲明,通常不建議存放敏感信息,由於base64是對稱解密的,意味着該部分信息能夠歸類爲明文信息。定義一個payload:安全

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

而後將其進行base64加密,獲得JWT的第二部分。如eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9。

JWT的第三部分是一個簽證信息,這個簽證信息由三部分組成:1 header (base64後的),2 payload (base64後的),3 secret。這個部分須要base64加密後的header和base64加密後的payload使用.鏈接組成的字符串,而後經過header中聲明的加密方式進行加鹽secret組合加密,而後就構成了jwt的第三部分。

注意:secret是保存在服務器端的,jwt的簽發生成也是在服務器端的,secret就是用來進行jwt的簽發和jwt的驗證,因此,它就是你服務端的私鑰,在任何場景都不該該流露出去。一旦客戶端得知這個secret, 那就意味着客戶端是能夠自我簽發jwt了。

首先須要安裝拓展 pip install djangorestframework-jwt,而後在django進行配置,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 擴展的說明文檔中提供了手動簽發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)

在註冊時,引入上述代碼,簽發JWT便可。而對於登陸,JWT拓展提供了內置的視圖,在urls中添加對於路由便可。

from rest_framework_jwt.views import obtain_jwt_token

urlpatterns = [
    url(r'^authorizations/$', obtain_jwt_token),
]

雖然寫起來很簡單,可是內部其實作了不少的事,今天就來詳細研究一下,源代碼內部作了哪些事情。

當用戶登陸,會以post形式發請求到後端,會訪問obtain_jwt_token中的post方法,在源代碼中能夠看到obtain_jwt_token = ObtainJSONWebToken.as_view(),跟咱們寫的類視圖十分相似,這是一個內部已經寫好的類視圖。

 

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

 

該類並未定義任何方法,因此對應的post方法應該寫在父類,下面是父類中的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)

 

上述方法返回一個Response對象,通過一系列操做返回到前端,訪問結束。

而在DRF框架中,在調用視圖以前,就會進行相應的驗證操做。想要了解整個過程,須要咱們從源代碼中一步步去探索。當前端發起一個請求到後端,會根據路由訪問對象的視圖類的as_view()方法,該方法會接着調用dispatch()方法,APIView是DRF中全部視圖類的父類,能夠看一下他的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

能夠看到,到請求進來,會調用self.initalize_request()方法對請求進行處理。

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
        )

咱們須要關注的只有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]

接着往上照,能夠看到類屬性permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES,這就是要什麼咱們要在django配置中加入DEFAULT_PERMISSION_CLASSES配置,上述方法會遍歷咱們在配置中寫到的列表,拿到裏面的驗證類,並進行實例化,並將生成的對象裝在一個新的列表中,保存在新生成的Request對象中。而後咱們接着看dispatch方法,在實際調用視圖類中的對應方法前,還調用了self.initial()方法進行一些初始化操做。

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)

咱們須要關注的是最後三排,分別調用了三個方法,進行身份驗證,權限驗證和分流操做,這裏咱們只關注 身份驗證方法,self.perform_authentication(),該方法內只有一句代碼request.user,看起來像是在調用request對象的user屬性,其實否則,咱們能夠到DRF框架的Request對象中找到如下方法。

@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方法通過property裝飾器裝飾後,就能夠像一個屬性同樣調用該方法,該方法在Request對象中存在對應的user時會直接返回,若用戶登錄時,Request對象中沒有對應的user,因此代碼會走if判斷裏面,咱們只須要關注方法self._authenticate()的調用便可。

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()

能夠看到,該方法會遍歷咱們在以前處理Request對象時傳入的裝着驗證類對象的列表,並調用驗證類的authenticate()方法,若驗證成功生成對應的self.user和self.auth並直接return,往上則直接將生成的self.user進行返回,若驗證內部出錯,會調用self._not_authenticated(),並拋出錯誤,往上看,在dispatch方法中,若初始化方法出錯,則進行捕獲,並調用self.handle_exception()方法生成一個Response對象進行返回,不會執行視圖類中對應的方法,則調用對於的是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默認配置中爲一個匿名用戶的類,UNAUTHENTICATED_TOKEN默認爲None,若全部驗證都爲經過或者某一驗證過程當中出錯,則生成一個匿名用戶,並將self.auth設置爲None。

綜上所述,在請求執行以前,DRF框架會根據咱們在配置文件中配置的驗證類對用戶進行身份驗證,若未經過驗證,則會生成一個匿名用戶,驗證經過,則生成對應的用戶。

個人QQ:595395786。想交流的能夠加一下!!!

 

self._not_authenticated(),

原文出處:https://www.cnblogs.com/HZHST/p/11332118.html

相關文章
相關標籤/搜索