Django JWT

參考基于 Token 的身份驗(yàn)證、JSON Web Token - 在Web應(yīng)用間安全地傳遞信息、基于cookie的django-rest-jwt認(rèn)證

前兩篇是關(guān)于原理的,最后一篇是和django-restframework相關(guān)的。

JWT原理

除了使用session外,還可以使用token進(jìn)行用戶認(rèn)證(Authentication)。
一個(gè)很大的區(qū)別是,session需要在服務(wù)端存儲(chǔ)能夠通過session_id而獲取的信息,每次請(qǐng)求到達(dá)服務(wù)端時(shí),需要根據(jù)session_id這個(gè)key值,獲取存儲(chǔ)在內(nèi)存/磁盤/數(shù)據(jù)庫中的信息。而token的話,信息均在token里面,服務(wù)端只需要根據(jù)token中定義的算法進(jìn)行解析,即可獲得所需認(rèn)證信息。所以一個(gè)是memory cost,一個(gè)是time cost。
現(xiàn)在使用token比較流行。

JWT(Json Web Token)是實(shí)現(xiàn)token認(rèn)證的一種通用標(biāo)準(zhǔn)。
JWT分為三部分:header,payload,signature。中間用點(diǎn)分開,均使用base64進(jìn)行編碼,所以看起來像是這樣:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VyX3R5cGUiOjEsIm5iZiI6MTUyNjg4NjYzM30.CTZH48xD_TdtDZcgAd8exiCxkryXASruDCbRHsFFD5Y

header基本固定,包含token使用的類型,使用的算法等。

payload是具體信息,有些字段是標(biāo)準(zhǔn)字段,當(dāng)然也可以依據(jù)需求添加自定義的字段,非常方便。為了每次請(qǐng)求獲取的jwt token稍有不同,我會(huì)在payload中加入一些合時(shí)間有關(guān)的字段,比如exp(Expiration time,過期時(shí)間),iat(Issued at,發(fā)行時(shí)間),nbf(Not before)。

signature是把前兩部分使用base64編碼后,用"."連接,然后使用在header中定義的算法(默認(rèn)是HS256)進(jìn)行加密。此過程需要額外提供一個(gè)密鑰(secret)。

三部分拼起來就是jwt token。由于有時(shí)間信息,payload和signature總是會(huì)變的。

和Django結(jié)合

一、REST

一般而言是在restful的接口中使用jwt token,相關(guān)的Django庫是djangorestframeworkdjangorestframework-jwt。

需要在setting中進(jìn)行配置:

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'common.permissions.IsAuthenticated',
    ],
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'common.authentication.CookieJSONWebTokenAuthentication',
    ],
}

設(shè)置項(xiàng)DEFAULT_AUTHENTICATION_CLASSES是用來進(jìn)行用戶認(rèn)證的,因?yàn)榈谌綆炷J(rèn)token保存在http header中,所以自定義一個(gè)類,將token保存到cookie中。
這個(gè)類可以參考rest_framework_jwt.authentication.JSONWebTokenAuthentication。
需要實(shí)現(xiàn)兩個(gè)方法:

  • authenticate(self, request)
    邏輯是:從request中獲取token,從token中獲取payload,從payload中獲取用戶認(rèn)證信息。
  • authenticate_header(self, request)
    作用是:當(dāng)需要返回401 Unauthenticated403 Permission Denied時(shí),如何配置http header中的WWW-Authenticate項(xiàng)。

當(dāng)在rest_framework中訪問request.user時(shí),調(diào)用此方法。
具體如下。
代碼在rest_framework.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

self._authenticate()中,self.authenticators即是在settings中設(shè)置的。

    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ì)發(fā)現(xiàn)是在rest_framework.views中的APIView類中的dispatch方法來調(diào)用的。

另一個(gè)設(shè)置項(xiàng)是DEFAULT_PERMISSION_CLASSES,做的是用戶授權(quán)(Authorization)判斷,由于該設(shè)置項(xiàng)的存在,所以所有的APIView均會(huì)做用戶鑒權(quán)判斷。自定義類可參考rest_framework.permissions.IsAuthenticated,目前這個(gè)類僅做了該用戶是否已a(bǔ)uthenticated的判斷,若有其他方面權(quán)限的判斷,可以在具體的view中加裝飾器。
比如,判斷用戶是否是雇員(我的request.user是一個(gè)dict):

class IsEmployeeUser(BasePermission):
    """
    Allows access only to employee users.
    """

    def has_permission(self, request, view):
        return request.user and request.user['is_authenticated'] and request.user['is_employee']

然后,裝飾器:

from rest_framework.decorators import permission_classes
def required(func):
    return permission_classes([IsEmployeeUser])(func)

具體的授權(quán)判斷,同認(rèn)證判斷一樣,均是在rest_framework.views中的APIView類中的dispatch方法中,具體是調(diào)用check_permissions(self, request)方法,其中的self.get_permissions()就是取的settings中的設(shè)置。

因?yàn)橛心J(rèn)的授權(quán)判斷類,所以如果不需要進(jìn)行授權(quán)判斷,裝飾器中應(yīng)該這樣寫

def anonymous(func):
    permission_classes([])(func)

二、HTML

使用以上定義的方法,只會(huì)對(duì)restful的接口進(jìn)行認(rèn)證和授權(quán)判斷。
若想對(duì).html.js等也想使用jwt token,首先需要配置settings:

MIDDLEWARE = [
    ...
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'common.middleware.AuthenticationMiddleware',
    ...
]

在默認(rèn)的AuthenticationMiddleware下添加自定義的中間件。
代碼如下,參考的是默認(rèn)的django.contrib.auth.middleware.AuthenticationMiddleware,其實(shí)也是一個(gè)Django中間件的普通實(shí)現(xiàn):

class AuthenticationMiddleware(MiddlewareMixin):

    def process_request(self, request):
        assert hasattr(request, 'session'), (
            "The svc_auth authentication middleware requires session middleware "
            "to be installed. Edit your MIDDLEWARE%s setting to insert "
            "'django.contrib.sessions.middleware.SessionMiddleware' before "
            "'svc_auth.middleware.AuthenticationMiddleware'."
        ) % ("_CLASSES" if settings.MIDDLEWARE is None else "")
        user, _ = authenticator.authenticate(request=request)
        if user is not None:
            request.user = user

其中authenticator就是上一節(jié)的CookieJSONWebTokenAuthentication的實(shí)例。

這樣就完成了用戶認(rèn)證的判斷。

接下來是用戶授權(quán)的判斷,參考的是django.contrib.auth.decorators中的login_required。

def required(function=None, redirect_field_name=REDIRECT_FIELD_NAME, login_url=None):
    actual_decorator = user_passes_test(
        lambda user: user and user.get('is_authenticated', False) and user.get('is_employee', False),
        login_url=login_url() if login_url else SimpleLazyObject(lambda: default_login_url('employee')),
        redirect_field_name=redirect_field_name,
    )
    if function:
        return actual_decorator(function)
    return actual_decorator

這樣,在需要的view中添加@required即可。

與REST的設(shè)置不同,沒有默認(rèn)的配置,所以如果沒有添加裝飾器的話,不會(huì)去進(jìn)行授權(quán)判斷,也就是說,不需要有anonymous裝飾器。

三、關(guān)于User

如果使用的Django的User,那么以上很多都不用自定義。而如果需要使用自定義的User,那么就需要對(duì)每一個(gè)獲取user對(duì)象的地方進(jìn)行重寫。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容