麦子学院 2017-04-09 22:24
优化Django Rest Framework 的Token验证功能
回复:0 查看:3037
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.utils.timezone
import utc
from django.conf
import settings
from rest_framework
import status
from rest_framework.response
import Response
from rest_framework.authtoken.models
import Token
fromrest_framework.authtoken.views
import ObtainAuthToken
EXPIRE_MINUTES = getattr(settings, 'REST_FRAMEWORK_TOKEN_EXPIRE_MINUTES', 1)
class
ObtainExpiringAuthToken(ObtainAuthToken):
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'])
utc_now = datetime.datetime.utcnow().replace(tzinfo=utc)
if created
or token.created < utc_now - datetime.timedelta(minutes=EXPIRE_MINUTES):
token.delete()
token = Token.objects.create(user=serializer.validated_data['user'])
token.created = utc_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.utils.timezone
import utc
from django.conf
import settings
fromrest_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.'))
utc_now = datetime.datetime.utcnow().replace(tzinfo=utc)
if token.created < utc_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)#
来源:
小马