优化Django Rest Framework 的Token验证功能

Posted DevOps

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了优化Django Rest Framework 的Token验证功能相关的知识,希望对你有一定的参考价值。

api的通信采用token + ssl,简化和方便线上脚本的调用。Django版本1.8.16,djangorestframework版本3.5.3,用了框架提供的rest_framework.authtoken.views.obtain_auth_token和rest_framework.authentication.TokenAuthentication后,发现了一个问题,前者认证通过创建token后,这个token就不会自动更新了,非常不安全,非常危险。后者验证时候是不带缓存的,需要查询数据库,由于每次请求都要验证token,请求相当频繁,感觉不是很爽。

1、实现生成的token带过期时间
首先在setting.py配置文件设置过期时间 REST_FRAMEWORK_TOKEN_EXPIRE_MINUTES,这里设置为60分钟

REST_FRAMEWORK_TOKEN_EXPIRE_MINUTES = 60

setting.py同目录文件view.py编辑一个视图


#coding=utf8

import datetime

from django.conf import settings

from rest_framework import status

from rest_framework.response import Response

from rest_framework.authtoken.models import Token

from rest_framework.authtoken.views import ObtainAuthToken

 

EXPIRE_MINUTES = getattr(settings, 'REST_FRAMEWORK_TOKEN_EXPIRE_MINUTES', 1)

 

class ObtainExpiringAuthToken(ObtainAuthToken):

    """Create user token"""

    def post(self, request):

        serializer = self.serializer_class(data=request.data)

        if serializer.is_valid():

            token, created =  Token.objects.get_or_create(user=serializer.validated_data['user'])

 

            time_now = datetime.datetime.now()

 

            if created or token.created < time_now - datetime.timedelta(minutes=EXPIRE_MINUTES):

                # Update the created time of the token to keep it valid

                token.delete()

                token = Token.objects.create(user=serializer.validated_data['user'])

                token.created = time_now

                token.save()

 

            return Response({'token': token.key})

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

 

obtain_expiring_auth_token = ObtainExpiringAuthToken.as_view()


url.py新增url用于生成用户token


#from rest_framework.authtoken.views import obtain_auth_token

from .views import obtain_expiring_auth_token

 

urlpatterns += [

    #url(r'^api/token/', obtain_auth_token, name='api-token'),

    url(r'^api/token/', obtain_expiring_auth_token, name='api-token'),

]


用curl测试接口 api/token/


git:(master) ✗ curl -H "Content-Type: application/json" -X POST -d '{"username":"test","password":"test"}' http://127.0.0.1:9000/api/token/

{"token":"6ff54785241f825846e4c5fca61cceb6be7f911e"}%


然后,然后这个生成token的接口就好了。目前还有一个问题,用户就是生成一个token例如A,然后用户再也不来请求这个接口生成token,那么这个用户的token A也会一直生效且不会被更新,那么要需要结合token验证函数,来强制删除用户过期的token。

2、自定义token验证,强制删除过期的token,顺便缓存下没有过期的token
首先在setting.py文件新增全局认证类api.authentication.ExpiringTokenAuthentication替换默认的rest_framework.authentication.TokenAuthentication

REST_FRAMEWORK = {

    'DEFAULT_AUTHENTICATION_CLASSES': [

        'rest_framework.authentication.BasicAuthentication',

        #'rest_framework.authentication.TokenAuthentication',  #enable Token authentication

        'api.authentication.ExpiringTokenAuthentication'

    ],

    'PAGE_SIZE': 10,

}


新建authentication.py文件,改文件在api这个目录下面。


#coding=utf8

import datetime

from django.conf import settings

from rest_framework.authentication import TokenAuthentication

from rest_framework import exceptions

from django.utils.translation import ugettext_lazy as _

 

from django.core.cache import cache

 

EXPIRE_MINUTES = getattr(settings, 'REST_FRAMEWORK_TOKEN_EXPIRE_MINUTES', 1)

 

class ExpiringTokenAuthentication(TokenAuthentication):

    """Set up token expired time"""

    def authenticate_credentials(self, key):

        # Search token in cache

        cache_user = cache.get(key)

        if cache_user:

            return (cache_user, key)

 

        model = self.get_model()

        try:

            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.'))

 

        time_now = datetime.datetime.now()

 

        if token.created < time_now - datetime.timedelta(minutes=EXPIRE_MINUTES):

            token.delete()

            raise exceptions.AuthenticationFailed(_('Token has expired then delete.'))

 

        if token:

            # Cache token

            cache.set(key, token.user, EXPIRE_MINUTES * 60)

 

        return (token.user, token)


然后然后,所有的功能都实现了,删除用户过期的token和缓存token减少数据库查询。



欢迎关注运维自研堂订阅号,运维自研堂是一个技术分享平台,主要是运维自动化开发:python、django、saltstack、tornado、bootstrap、redis、golang、docker、etcd等经验分享。


开源    创新     共享


投稿&商务合作

Mail:idevops168@163.com       QQ:785249378


牛人并不可怕,可怕的是牛人比我们还努力!





以上是关于优化Django Rest Framework 的Token验证功能的主要内容,如果未能解决你的问题,请参考以下文章

django框架学习六:优化views.py文件,使用rest_framework中的APIVew和Response返回

如何优化 Django REST Framework 的性能

Django Rest Framework,数据库查询优化

Django学习之Rest Framework 的Token验证功能优化详解

是否过滤 Django Rest Framework (DRF) 中的代码示例优化问题

在 Django REST framework 善用 SerializerMethodField 来优化不必要的查询