二、django rest_framework原始碼之認證流程剖析

奧辰發表於2018-11-19

1.緒言

        上一篇中講了django rest_framework總體流程,整個流程中最關鍵的一步就是執行dispatch方法。在dispatch方法中,在呼叫了一個initial方法,所有的認證、許可權檢查、訪問頻率控制都是在這個方法中進行的。下面程式碼為init方法執行這三個操作的原始碼:

def initial(self, request, *args, **kwargs):
    """
    在呼叫方法處理程式之前執行需要發生的任何事情(例如:認證、許可權、訪問頻率控制)。
    """
    ……
    self.perform_authentication(request)#執行認證
    self.check_permissions(request)#檢查許可權
    self.check_throttles(request)#頻率控制

        這一篇我們來分析一下django rest_framework認證部分的原始碼。

2. Request物件

        這裡為什麼提到Request物件呢?因為接下來的操作跟它息息相關。我們可以把整個瀏覽器到伺服器的請求過程看作快遞運輸,Request物件就是我們要運送的包裹,所經歷的各個方法就猶如快遞中轉站或者安檢口,會檢查Request物件是否符合規定,如果符合規範稍作加工就放行,移送下一個中轉站,如果不符合規範,就打回原籍。認證(authentication)顯然就是一個安檢口,而且還是第一道安檢。我們先來看一看這一道安檢的入口,即perform_authentication方法的原始碼:

def perform_authentication(self, request):

       request.user

        你沒有看錯,perform_authentication方法在刪除註釋之後就只有兩行程式碼。程式碼中的request就是我們要運送的主角(Request物件),是在dispatch方法中第一步操作加工之後獲得的,裡面封裝了原生的request物件(快遞包裹中真正要寄送的東西),認證類物件(寄件人身份檢查方法),以及其他的一些功能函式。這裡的request.user就是封裝在Request中的一個方法(可不是屬性變數),我們來看看user方法的原始碼:

def user(self):

    if not hasattr(self, `_user`):

        with wrap_attributeerrors():

            self._authenticate()

    return self._user

        在user中,如果有“_user”屬性,直接返回這個屬性,如果沒有呼叫“_authenticate”方法,再來看看“_authenticate”方法:

def _authenticate(self):

    # 這裡的authenticators就是封裝在Request物件中的認證物件(在dispatch第一步操作中封裝進去的)

    #這個authenticators就猶如快遞運送中的寄件人身份核驗規範,而且這個規範可能不止一個

    for authenticator in self.authenticators:#遍歷每一個認證例項

        try:

            # 執行認證例項中的認證方法(快遞開始安檢)

            user_auth_tuple = authenticator.authenticate(self)#返回一個tuple

        except exceptions.APIException:#如果安檢沒有通過

            self._not_authenticated()

            raise   #丟擲異常

        if user_auth_tuple is not None:#如果在安檢中得到了寄件人的身份資訊(也就是那個tuple不為空)

            self._authenticator = authenticator

            self.user, self.auth = user_auth_tuple#在包裹(Request)中新增寄件人資訊和核驗資訊

            return  #返回None

    self._not_authenticated()#如果認證類設定為空
        再來看看認證例項的authenticate方法,我們以TokenAuthentication類為例進行分析,其他的認證類流程大體一致。貼出TokenAuthentication類的authenticate方法原始碼:
def authenticate(self, request):

    #拿到認證資訊(token資訊)

    #形如:Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b

    auth = get_authorization_header(request).split()#得到[`Token`, `9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b`]

    #如果認證資訊為空,或者第一個元素不是`Token’

    if not auth or auth[0].lower() != self.keyword.lower().encode():

        return None

    #如果拿到的認證資訊不是兩個元素

    if len(auth) == 1:

        msg = _(`Invalid token header. No credentials provided.`)

        raise exceptions.AuthenticationFailed(msg)

    elif len(auth) > 2:

        msg = _(`Invalid token header. Token string should not contain spaces.`)

        raise exceptions.AuthenticationFailed(msg)

    try:

        token = auth[1].decode()#對第二個元素進行解碼

    except UnicodeError:

        msg = _(`Invalid token header. Token string should not contain invalid characters.`)

        raise exceptions.AuthenticationFailed(msg)

    return self.authenticate_credentials(token)
def authenticate_credentials(self, key):

    model = self.get_model()#token模型類

    try:

        # 根據token在資料庫中進行核驗

        #這一步操作就類似於對包裹中的寄件人資訊與公安局的身份證號碼進行比對

        #下面的token是一個token模型類,外來鍵關聯user表,可以拿到user資訊

        token = model.objects.select_related(`user`).get(key=key)

    except model.DoesNotExist:#如果在公安局的資料庫中找不到這個寄件人資訊

        raise exceptions.AuthenticationFailed(_(`Invalid token.`))

    if not token.user.is_active:#使用者未啟用

        raise exceptions.AuthenticationFailed(_(`User inactive or deleted.`))

    return (token.user, token)#返回一個tuple
        至此,認證例項裡面的authenticate方法就分析完了。我們在回到_authenticate方法,如果有認證例項列表(self.authenticators)不為空就執行authenticate方法,如果認證例項列表為空或者,或者執行authenticate方法認證失敗,就執行_not_authenticated方法,來看看_not_authenticated方法原始碼:
def _not_authenticated(self):

    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

        如下是在restframework的settings.py檔案中對UNAUTHENTICATED_USER和UNAUTHENTICATED_TOKEN預設設定:

# Authentication

`UNAUTHENTICATED_USER`: `django.contrib.auth.models.AnonymousUser`,

`UNAUTHENTICATED_TOKEN`: None,

        這些設定可以在自己專案檔案的settings.py檔案中進行重新配置。_not_authenticated方法也是直接修改Request物件中的物件和token,然後返回None。_not_authenticated方法執行完之後,一個認證就完成了,如果認證例項列表還有其他元素,就繼續下一個認證,認證流程是一樣的,如果所有認證例項都遍歷完成,那麼整個認證流程就跑完了,Request物件中就包含了認證之後的認證資訊。

3. 自定義認證類

    上面分析的是djangorestframework自帶的認證類,如果我們要自己建立認證類該怎麼做呢?從上面認證類分析中介紹到一個名為authenticate的方法,這個方法在執行認證的時候回被呼叫,所以自定義認證類的時候,這個方法是必須建立的,另外還有一個名為authenticate_header的方法,也是必須寫的。djangorestframework有一個名為BaseAuthentication的類,這是所有認證類的父類,最好繼承這個類(也可以不繼承,但是那兩個方法必須包含)。如下程式碼是自定義的一個認證類:
class MyTokenAuthentication(BaseAuthentication):

    ```自定義的認證類```

    def authenticate(self, request):

        token = request.GET.get(`token`)

        success=check_token(token)

        if success:

            return

        else:

            raise AuthenticationFailed(`認證失敗`)

    def authenticate_header(self, request):

        pass

        建立好認證類之後如何用上呢,這就涉及到配置認證類了。

4. 配置認證類

        在檢視中,如果某個類需要指定認證類,該如何進行配置呢?有兩種方法——區域性配置和全域性配置。

        區域性配置是隻在配置的當前檢視類中生效,配置方法時在試圖類中加入下面程式碼:

authentication_classes = [MyTokenAuthentication , ]

        注意:當認證類只有一個時,列表末尾一定要加一個逗號。在區域性配置了認證類之後,restframework就只會讀取當前配置的認證類,忽略預設設定。

        全域性配置是在專案的settings.py檔案中進行配置,這樣的話所有檢視都會執行該配置(除非該檢視自定義了區域性配置)。配製方法是在專案settings.py檔案中REST_FRAMEWORK中加入如下程式碼:

REST_FRAMEWORK={

  "DEFAULT_AUTHENTICATION_CLASSES":["myapp.auth. MyTokenAuthentication ",]

}

        同樣的,當認證類只有一個時,列表末尾一定要加一個逗號,列表中的元素是認證類的路徑。

相關文章