web-dev-qa-db-ger.com

Token-Authentifizierung für RESTful-API: Soll das Token regelmäßig geändert werden?

Ich baue eine RESTful-API mit Django und Django-Rest-Framework auf.

Als Authentifizierungsmechanismus haben wir "Token Authentication" gewählt und ich habe es bereits nach der Dokumentation von Django-REST-Framework implementiert. Sollte es die mobile App sein, die eine Erneuerung des Tokens erfordert, oder die Web-App sollte dies autonom tun?

Was ist die beste Praxis?

Hat hier jemand Erfahrung mit Django REST Framework gemacht und könnte eine technische Lösung vorschlagen?

(die letzte Frage hat eine niedrigere Priorität)

92
nemesisdesign

Es wird empfohlen, dass mobile Clients ihr Authentifizierungstoken regelmäßig erneuern. Dies ist natürlich Sache des Servers zu erzwingen.

Die standardmäßige TokenAuthentication-Klasse unterstützt dies nicht. Sie können sie jedoch erweitern, um diese Funktionalität zu erreichen.

Zum Beispiel:

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

Es ist auch erforderlich, die Standard-Login-Ansicht des Rest-Frameworks zu überschreiben, damit das Token bei jeder Anmeldung aktualisiert wird:

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()

Und vergessen Sie nicht, die URLs zu ändern:

urlpatterns += patterns(
    '',
    url(r'^users/login/?$', '<path_to_file>.obtain_expiring_auth_token'),
)
81
odedfos

Wenn jemand an dieser Lösung interessiert ist, aber ein Token haben möchte, das für eine bestimmte Zeit gültig ist, wird durch ein neues Token ersetzt hier ist die vollständige Lösung (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')
)

ihr Projekt urls.py (im Array 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)

Fügen Sie in Ihren REST_FRAMEWORK-Einstellungen ExpiringTokenAuthentication als Authentifizierungsklasse anstelle von TokenAuthentication hinzu:

REST_FRAMEWORK = {

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

Ich habe versucht, @odedfos zu beantworten, aber Ich hatte irreführende Fehler . Hier ist die gleiche Antwort, fest und mit korrekten Importen.

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)
5
Benjamin Toueg

Sie können http://getblimp.github.io/Django-rest-framework-jwt nutzen.

Diese Bibliothek kann ein Token generieren, das ein Ablaufdatum hat

Um den Unterschied zwischen dem DRF-Standard-Token und dem von der DRF bereitgestellten Token zu verstehen, werfen Sie einen Blick auf:

Wie kann man Django REST JWT-Authentifizierungsskala mit mehreren Webservern erstellen?

3
Angky William

Wenn Sie feststellen, dass ein Token wie ein Sitzungscookie ist, können Sie sich an die Standardlebensdauer von Sitzungscookies in Django halten: https://docs.djangoproject.com/de/1.4/ref/settings/#session-cookie- Alter .

Ich weiß nicht, ob Django Rest Framework das automatisch erledigt, aber Sie können immer ein kurzes Skript schreiben, das die veralteten herausfiltert und als abgelaufen markiert.

1

Ich dachte, ich würde mit DRY eine Django 2.0-Antwort geben. Jemand hat dies bereits für uns entwickelt, google Django OAuth ToolKit. Erhältlich mit pip, pip install Django-oauth-toolkit. Anweisungen zum Hinzufügen der Token-ViewSets mit Routern: https://Django-oauth-toolkit.readthedocs.io/de/latest/rest-framework/getting_started.html . Es ist ähnlich wie das offizielle Tutorial.

Im Grunde war OAuth1.0 mehr Sicherheit von gestern, was TokenAuthentication ist. OAuth2.0 ist heutzutage in aller Munde. Sie erhalten eine AccessToken-, eine RefreshToken- und eine Gültigkeitsbereichsvariable, um die Berechtigungen zu optimieren. Sie erhalten am Ende Creds wie folgt:

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

Ich dachte nur, ich würde meine hinzufügen, da dies für mich hilfreich war. Ich gehe normalerweise mit der JWT-Methode, aber manchmal ist so etwas besser. Ich habe die akzeptierte Antwort für Django 2.1 mit korrekten Importen aktualisiert.

authentifizierung.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)
0
wdfc

Der Autor hat gefragt

die Frage ist, sollte die Anwendung das Token regelmäßig erneuern/ändern, und wenn ja, wie? Sollte es die mobile App sein, die eine Erneuerung des Tokens erfordert, oder die Web-App sollte dies autonom tun?

In allen Antworten wird jedoch beschrieben, wie das Token automatisch geändert wird. 

Ich denke, dass Token durch Token regelmäßig geändert wird, ist bedeutungslos. Das Rest-Framework erstellt ein Token mit 40 Zeichen. Wenn der Angreifer jede Sekunde 1000 Token testet, sind 16**40/1000/3600/24/365=4.6*10^7 Jahre erforderlich, um das Token abzurufen. Sie sollten sich keine Sorgen machen, dass der Angreifer Ihren Token einzeln testen kann. Selbst wenn Sie Ihr Token geändert haben, ist die Wahrscheinlichkeit, dass Sie Ihr Token erraten, die gleiche.

Wenn Sie befürchten, dass die Angreifer möglicherweise ein Token erhalten, so dass Sie es regelmäßig ändern, und nachdem der Angreifer das Token erhalten hat, kann er auch Ihr Token ändern, bevor der echte Benutzer herausgeschmissen wird. 

Was Sie wirklich tun sollten, ist zu verhindern, dass der Angreifer das Token Ihres Benutzers erhält - verwenden Sie https .

Ich sage übrigens nur, dass das Ändern des Tokens durch das Token bedeutungslos ist. Das Ändern des Tokens nach Benutzername und Kennwort ist manchmal gemein. Möglicherweise wird das Token in einer bestimmten http-Umgebung (Sie sollten diese Situation immer vermeiden) oder von einem Dritten (in diesem Fall sollten Sie eine andere Art von Token erstellen, verwenden Sie oauth2) und wenn der Benutzer gefährliche Dinge wie das Ändern ausführt Binden eines Postfachs oder Löschen eines Kontos. Stellen Sie sicher, dass Sie das Origin-Token nicht mehr verwenden, da es vom Angreifer mithilfe von Sniffer- oder Tcpdump-Tools aufgedeckt wurde.

0
ramwin