RESTful API的令牌身份validation:是否应定期更改令牌?
我正在用Django和django-rest-framework构build一个RESTful API。
作为authentication机制,我们select了“Token Authentication”,我已经在Django-REST-Framework的文档中实现了它,问题是,应用程序是否应该定期更新/更改令牌,如果是的话,是怎么做的? 应该是移动应用程序需要更新令牌还是Web应用程序应该自动执行?
最佳做法是什么?
任何人在这里经验与Django REST框架,并可以build议一个技术解决scheme?
(最后一个问题的优先级较低)
让移动客户端定期更新他们的身份validation令牌是一个很好的做法。 这当然取决于服务器执行。
默认的TokenAuthentication类不支持这个,但是你可以扩展它来实现这个function。
例如:
from rest_framework.authentication import TokenAuthentication, get_authorization_header from rest_framework.exceptions import AuthenticationFailed class ExpiringTokenAuthentication(TokenAuthentication): def authenticate_credentials(self, key): try: token = self.model.objects.get(key=key) except self.model.DoesNotExist: raise exceptions.AuthenticationFailed('Invalid token') if not token.user.is_active: raise exceptions.AuthenticationFailed('User inactive or deleted') # This is required for the time comparison utc_now = datetime.utcnow() utc_now = utc_now.replace(tzinfo=pytz.utc) if token.created < utc_now - timedelta(hours=24): raise exceptions.AuthenticationFailed('Token has expired') return token.user, token
还需要重写默认的其余框架login视图,以便在完成login时刷新令牌:
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']) if not created: # update the created time of the token to keep it valid token.created = datetime.datetime.utcnow() 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:
urlpatterns += patterns( '', url(r'^users/login/?$', '<path_to_file>.obtain_expiring_auth_token'), )
如果有人对这个解决scheme感兴趣,但是想要一个有效的令牌,那么这个令牌会被一个新的令牌取代,这里就是完整的解决scheme(Django 1.6):
yourmodule / views.py:
import datetime from django.utils.timezone import utc from rest_framework.authtoken.views import ObtainAuthToken from rest_framework.authtoken.models import Token from django.http import HttpResponse import json 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.object['user']) utc_now = datetime.datetime.utcnow() if not created and token.created < utc_now - datetime.timedelta(hours=24): token.delete() token = Token.objects.create(user=serializer.object['user']) token.created = datetime.datetime.utcnow() token.save() #return Response({'token': token.key}) response_data = {'token': token.key} return HttpResponse(json.dumps(response_data), content_type="application/json") return HttpResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST) obtain_expiring_auth_token = ObtainExpiringAuthToken.as_view()
yourmodule / urls.py:
from django.conf.urls import patterns, include, url from weights import views urlpatterns = patterns('', url(r'^token/', 'yourmodule.views.obtain_expiring_auth_token') )
您的项目urls.py(在urlpatterns数组中):
url(r'^', include('yourmodule.urls')),
yourmodule / authentication.py:
import datetime from django.utils.timezone import utc from rest_framework.authentication import TokenAuthentication from rest_framework import exceptions class ExpiringTokenAuthentication(TokenAuthentication): def authenticate_credentials(self, key): try: token = self.model.objects.get(key=key) except self.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() if token.created < utc_now - datetime.timedelta(hours=24): raise exceptions.AuthenticationFailed('Token has expired') return (token.user, token)
在您的REST_FRAMEWORK设置中,将ExpiringTokenAuthentication添加为validation类而不是TokenAuthentication:
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.SessionAuthentication', #'rest_framework.authentication.TokenAuthentication', 'yourmodule.authentication.ExpiringTokenAuthentication', ), }
我试过@odedfos答案,但我误导了错误 。 这是相同的答案,固定和适当的import。
views.py
from django.utils import timezone 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 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.object['user']) if not created: # update the created time of the token to keep it valid token.created = datetime.datetime.utcnow().replace(tzinfo=utc) token.save() return Response({'token': token.key}) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
authentication.py
from datetime import timedelta from django.conf import settings from django.utils import timezone from rest_framework.authentication import TokenAuthentication from rest_framework import exceptions EXPIRE_HOURS = getattr(settings, 'REST_FRAMEWORK_TOKEN_EXPIRE_HOURS', 24) class ExpiringTokenAuthentication(TokenAuthentication): def authenticate_credentials(self, key): try: token = self.model.objects.get(key=key) except self.model.DoesNotExist: raise exceptions.AuthenticationFailed('Invalid token') if not token.user.is_active: raise exceptions.AuthenticationFailed('User inactive or deleted') if token.created < timezone.now() - timedelta(hours=EXPIRE_HOURS): raise exceptions.AuthenticationFailed('Token has expired') return (token.user, token)
您可以利用http://getblimp.github.io/django-rest-framework-jwt
该库能够生成具有到期date的令牌
要理解DRF默认令牌和DRF提供的令牌之间的区别,请查看:
如何使用多个web服务器使Django REST JWT身份validation缩放?
如果您注意到令牌像会话cookie那么您可以使用Django中的会话cookie的默认生命周期: https : //docs.djangoproject.com/en/1.4/ref/settings/#session-cookie-age 。
我不知道Django Rest Framework是否自动处理,但是你总是可以编写一个简短的脚本来过滤掉过时的脚本,并将它们标记为已过期。