我正在使用Django和django-rest-framework构建RESTful API 。
作为身份验证机制,我们选择了“令牌身份验证”,而我已经按照Django-REST-Framework的文档进行了实现,问题是,应用程序是否应该定期更新/更改令牌,如果可以,怎么做?是需要续签令牌的移动应用程序,还是应该由网络应用程序自主执行?
最佳做法是什么?
有人在Django REST Framework方面经验丰富,可以提出技术解决方案吗?
(最后一个问题的优先级较低)
我正在使用Django和django-rest-framework构建RESTful API 。
作为身份验证机制,我们选择了“令牌身份验证”,而我已经按照Django-REST-Framework的文档进行了实现,问题是,应用程序是否应该定期更新/更改令牌,如果可以,怎么做?是需要续签令牌的移动应用程序,还是应该由网络应用程序自主执行?
最佳做法是什么?
有人在Django REST Framework方面经验丰富,可以提出技术解决方案吗?
(最后一个问题的优先级较低)
Answers:
最好让移动客户端定期更新其身份验证令牌。这当然要由服务器来实施。
默认的TokenAuthentication类不支持此功能,但是您可以对其进行扩展以实现此功能。
例如:
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
还需要覆盖默认的rest框架登录视图,以便在登录完成后刷新令牌:
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()
并且不要忘记修改网址:
urlpatterns += patterns(
'',
url(r'^users/login/?$', '<path_to_file>.obtain_expiring_auth_token'),
)
如果某人对该解决方案感兴趣,但想拥有一个在一定时间内有效的令牌,则将其替换为新令牌,这里是完整的解决方案(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作为Authentification类,而不是TokenAuthentication:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
#'rest_framework.authentication.TokenAuthentication',
'yourmodule.authentication.ExpiringTokenAuthentication',
),
}
'ObtainExpiringAuthToken' object has no attribute 'serializer_class'
尝试访问api端点时出现错误。不知道我在想什么。
我已经尝试过@odedfos回答,但我误导了错误。这是相同的答案,已固定且已正确导入。
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)
以为我会用DRY给Django 2.0答案。谷歌Django OAuth ToolKit已经有人为我们建立了。可用pip 、pip install django-oauth-toolkit
。有关通过路由器添加令牌ViewSet的说明:https ://django-oauth-toolkit.readthedocs.io/en/latest/rest-framework/getting_started.html。它类似于官方教程。
因此,基本上,OAuth1.0具有更多的昨天的安全性,即TokenAuthentication。为了获得即将到期的令牌,如今最流行的是OAuth2.0。您将获得一个AccessToken,RefreshToken和scope变量来微调权限。您最终会获得如下信誉:
{
"access_token": "<your_access_token>",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "<your_refresh_token>",
"scope": "read"
}
作者问
问题是,应用程序是否应该定期更新/更改令牌,如果是,怎么办?是需要续签令牌的移动应用程序,还是应该由网络应用程序自主执行?
但是所有答案都在写关于如何自动更改令牌的信息。
我认为定期更改令牌是没有意义的。 其余框架将创建一个具有40个字符的令牌,如果攻击者每秒测试1000个令牌,则需要数16**40/1000/3600/24/365=4.6*10^7
年才能获得该令牌。您不必担心攻击者会一一测试您的令牌。即使您更改了令牌,猜测令牌的可能性也相同。
如果您担心攻击者可以获取您的令牌,那么您可以定期更改令牌,而不是在攻击者获取令牌之后,他也可以更改您的令牌,而不是将实际用户踢出。
您真正应该做的是使用https来防止攻击者获取用户的令牌。
顺便说一句,我只是说逐个令牌更改令牌是没有意义的,有时由用户名和密码更改令牌是有意义的。令牌可能在某些http环境(您应始终避免这种情况)或某些第三方(在这种情况下,您应创建其他类型的令牌,请使用oauth2)中使用,并且当用户执行某些危险的操作(如更改)时绑定邮箱或删除帐户后,应确保不再使用原始令牌,因为攻击者可能已使用嗅探器或tcpdump工具将其泄露。
您可以利用http://getblimp.github.io/django-rest-framework-jwt
该库能够生成具有到期日期的令牌
要了解DRF默认令牌和DRF提供的令牌之间的区别,请看一下:
如果您注意到令牌就像会话cookie,那么您可以坚持Django中会话cookie的默认生存期:https : //docs.djangoproject.com/en/1.4/ref/settings/#session-cookie-age。
我不知道Django Rest Framework是否会自动处理该问题,但是您始终可以编写一个简短的脚本来过滤掉过时的脚本并将其标记为过期。
只是以为我会加我的,因为这对我有帮助。我通常使用JWT方法,但有时这样会更好。我用适当的导入更新了django 2.1的可接受答案。
authentication.py
from datetime import timedelta
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
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.get_model().objects.get(key=key)
except ObjectDoesNotExist:
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
views.py
import datetime
from pytz import utc
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
from rest_framework.authtoken.serializers import AuthTokenSerializer
class ObtainExpiringAuthToken(ObtainAuthToken):
def post(self, request, **kwargs):
serializer = AuthTokenSerializer(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().replace(tzinfo=utc)
token.save()
return Response({'token': token.key})
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
只是为了继续添加到@odedfos答案,我认为语法已经进行了一些更改,因此ExpiringTokenAuthentication的代码需要进行一些调整:
from rest_framework.authentication import TokenAuthentication
from datetime import timedelta
from datetime import datetime
import datetime as dtime
import pytz
class ExpiringTokenAuthentication(TokenAuthentication):
def authenticate_credentials(self, key):
model = self.get_model()
try:
token = model.objects.get(key=key)
except 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.now(dtime.timezone.utc)
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
另外,不要忘记将其添加到DEFAULT_AUTHENTICATION_CLASSES而不是rest_framework.authentication.TokenAuthentication