Django JWT

参考基于 Token 的身份验证JSON Web Token - 在Web应用间安全地传递信息基于cookie的django-rest-jwt认证

前两篇是关于原理的,最后一篇是和django-restframework相关的。

JWT原理

除了使用session外,还可以使用token进行用户认证(Authentication)。
一个很大的区别是,session需要在服务端存储能够通过session_id而获取的信息,每次请求到达服务端时,需要根据session_id这个key值,获取存储在内存/磁盘/数据库中的信息。而token的话,信息均在token里面,服务端只需要根据token中定义的算法进行解析,即可获得所需认证信息。所以一个是memory cost,一个是time cost。
现在使用token比较流行。

JWT(Json Web Token)是实现token认证的一种通用标准。
JWT分为三部分:header,payload,signature。中间用点分开,均使用base64进行编码,所以看起来像是这样:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VyX3R5cGUiOjEsIm5iZiI6MTUyNjg4NjYzM30.CTZH48xD_TdtDZcgAd8exiCxkryXASruDCbRHsFFD5Y

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

payload是具体信息,有些字段是标准字段,当然也可以依据需求添加自定义的字段,非常方便。为了每次请求获取的jwt token稍有不同,我会在payload中加入一些合时间有关的字段,比如exp(Expiration time,过期时间),iat(Issued at,发行时间),nbf(Not before)。

signature是把前两部分使用base64编码后,用"."连接,然后使用在header中定义的算法(默认是HS256)进行加密。此过程需要额外提供一个密钥(secret)。

三部分拼起来就是jwt token。由于有时间信息,payload和signature总是会变的。

和Django结合

一、REST

一般而言是在restful的接口中使用jwt token,相关的Django库是djangorestframeworkdjangorestframework-jwt

需要在setting中进行配置:

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

设置项DEFAULT_AUTHENTICATION_CLASSES是用来进行用户认证的,因为第三方库默认token保存在http header中,所以自定义一个类,将token保存到cookie中。
这个类可以参考rest_framework_jwt.authentication.JSONWebTokenAuthentication
需要实现两个方法:

  • authenticate(self, request)
    逻辑是:从request中获取token,从token中获取payload,从payload中获取用户认证信息。
  • authenticate_header(self, request)
    作用是:当需要返回401 Unauthenticated403 Permission Denied时,如何配置http header中的WWW-Authenticate项。

当在rest_framework中访问request.user时,调用此方法。
具体如下。
代码在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中设置的。

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

再往上走,会发现是在rest_framework.views中的APIView类中的dispatch方法来调用的。

另一个设置项是DEFAULT_PERMISSION_CLASSES,做的是用户授权(Authorization)判断,由于该设置项的存在,所以所有的APIView均会做用户鉴权判断。自定义类可参考rest_framework.permissions.IsAuthenticated,目前这个类仅做了该用户是否已authenticated的判断,若有其他方面权限的判断,可以在具体的view中加装饰器。
比如,判断用户是否是雇员(我的request.user是一个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)

具体的授权判断,同认证判断一样,均是在rest_framework.views中的APIView类中的dispatch方法中,具体是调用check_permissions(self, request)方法,其中的self.get_permissions()就是取的settings中的设置。

因为有默认的授权判断类,所以如果不需要进行授权判断,装饰器中应该这样写

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

二、HTML

使用以上定义的方法,只会对restful的接口进行认证和授权判断。
若想对.html.js等也想使用jwt token,首先需要配置settings:

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

在默认的AuthenticationMiddleware下添加自定义的中间件。
代码如下,参考的是默认的django.contrib.auth.middleware.AuthenticationMiddleware,其实也是一个Django中间件的普通实现:

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就是上一节的CookieJSONWebTokenAuthentication的实例。

这样就完成了用户认证的判断。

接下来是用户授权的判断,参考的是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的设置不同,没有默认的配置,所以如果没有添加装饰器的话,不会去进行授权判断,也就是说,不需要有anonymous装饰器。

三、关于User

如果使用的Django的User,那么以上很多都不用自定义。而如果需要使用自定义的User,那么就需要对每一个获取user对象的地方进行重写。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值