REST风格的API令牌身份验证:应令牌定期更换?REST风格的API令牌身份验证:应令牌定期更换?

2019-05-12 12:07发布

我建设使用Django和一个RESTful API Django的休息框架 。

作为身份验证机制,我们选择了“令牌认证”,我已经实现了它下面的Django的REST框架的文档,问题是,如果应用程序更新/周期性的,如果改变令牌是怎么样? 它应该是移动应用程序需要被更新或web应用程序应该自动做记号呢?

什么是最好的做法是什么?

有人在这里经历了Django的REST框架,并可能提出一个技术解决方案?

(最后一个问题具有较低的优先级)

Answer 1:

这是很好的做法,移动客户端定期更新自己的身份验证令牌。 当然,这要由服务器来执行。

默认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'),
)


Answer 2:

如果有人有兴趣通过解决方案,但希望有一个令牌是有效的一段时间,然后得到由新的令牌 ,这里的完整解决方案(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(该URL模式阵列中):

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作为身份认证类代替TokenAuthentication:

REST_FRAMEWORK = {

    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.SessionAuthentication',
        #'rest_framework.authentication.TokenAuthentication',
        'yourmodule.authentication.ExpiringTokenAuthentication',
    ),
}


Answer 3:

我试过@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)


Answer 4:

您可以利用http://getblimp.github.io/django-rest-framework-jwt

这个库能够产生都有一个有效期令牌

要了解DRF默认的令牌,并通过DRF令牌提供之间的差异来看看:

如何让Django的REST JWT认证规模与多张网络服务器?



Answer 5:

想我给一个Django 2.0答案采用干。 有人已经建成了这一点,对我们来说,谷歌的Django OAuth的工具包。 可提供画中画, pip install django-oauth-toolkit 。 :与路由器加入令牌ViewSets说明https://django-oauth-toolkit.readthedocs.io/en/latest/rest-framework/getting_started.html 。 它类似于官方教程。

所以基本上OAuth1.0更昨日的安全是TokenAuthentication是什么。 获得幻想到期令牌,OAuth2.0的是所有的愤怒,这些天。 你会得到一个的accessToken,RefreshToken和范围可变微调的权限。 你最终像这样creds:

{
    "access_token": "<your_access_token>",
    "token_type": "Bearer",
    "expires_in": 3600,
    "refresh_token": "<your_refresh_token>",
    "scope": "read"
}


Answer 6:

如果您发现令牌就像一个会话cookie,那么你可以坚持会话cookie的在Django缺省生命周期: https://docs.djangoproject.com/en/1.4/ref/settings/#session-cookie-age 。

我不知道,如果Django的REST框架自动处理,但你总是可以写一个过滤掉过时的和过期标志着他们一个简短的脚本。



Answer 7:

The author asked

the question is, should the application renew / change the Token periodically and if yes how? Should it be the mobile app that requires the token to be renewed or the web-app should do it autonomously?

But all of the answers are writing about how to automatically change the token.

I think change token periodically by token is meaningless. The rest framework create a token that has 40 characters, if the attacker tests 1000 token every second, it requires 16**40/1000/3600/24/365=4.6*10^7 years to get the token. You should not worried that the attacker will test your token one by one. Even you changed your token, the probability of guess you token is the same.

If you are worried that maybe the attackers can get you token, so you change it periodically, than after the attacker get the token, he can also change you token, than the real user is kicked out.

What you should really do is to prevent tha attacker from getting your user's token, use https.

By the way, I'm just saying change token by token is meaningless, change token by username and password is sometimes meanful. Maybe the token is used in some http environment (you should always avoid this kind of situation) or some third party (in this case, you should create different kind of token, use oauth2) and when the user is doing some dangerous thing like changing binding mailbox or delete account, you should make sure you will not use the origin token anymore because it may has been revealed by the attacker using sniffer or tcpdump tools.



Answer 8:

只是想我要加我的,因为这对我来说是有帮助的。 我经常去的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)


文章来源: Token Authentication for RESTful API: should the token be periodically changed?