django - studio - web api ejemplo
Autenticación Token para API RESTful: ¿debería cambiarse el token periódicamente? (6)
Estoy construyendo una API RESTful con Django y django-rest-framework .
Como mecanismo de autenticación elegimos "Autenticación Token" y ya lo he implementado siguiendo la documentación de Django-REST-Framework, la pregunta es si la aplicación debe renovar / cambiar el Token periódicamente y, en caso afirmativo, ¿cómo? ¿Debería ser la aplicación móvil la que requiere la renovación del token o la aplicación web debería hacerlo de manera autónoma?
cual es la mejor practica?
¿Alguien aquí experimentó con Django REST Framework y podría sugerir una solución técnica?
(la última pregunta tiene una prioridad más baja)
Intenté la respuesta de @odedfos pero tuve un error engañoso . Aquí está la misma respuesta, arreglada y con importaciones apropiadas.
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)
Pensé que daría una respuesta Django 2.0 con DRY. Alguien ya lo construyó para nosotros, google Django OAuth ToolKit. Disponible con pip, pip install django-oauth-toolkit
. Instrucciones para agregar el token ViewSets con enrutadores: https://django-oauth-toolkit.readthedocs.io/en/latest/rest-framework/getting_started.html . Es similar al tutorial oficial.
Así que, básicamente, OAuth1.0 era más seguridad de ayer, que es lo que es TokenAuthentication. Para obtener tokens que expiran, OAuth2.0 está de moda en estos días. Obtiene una AccessToken, RefreshToken y una variable de ámbito para ajustar los permisos. Terminas con creds como este:
{
"access_token": "<your_access_token>",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "<your_refresh_token>",
"scope": "read"
}
Puede aprovechar http://getblimp.github.io/django-rest-framework-jwt
Esta biblioteca puede generar un token que tiene una fecha de vencimiento
Para entender la diferencia entre el token por defecto de DRF y el token proporcionado por el DRF, eche un vistazo a:
¿Cómo hacer que Django REST JWT Authentication scale con varios servidores web?
Si a alguien le interesa esa solución pero quiere tener un token que es válido durante un tiempo determinado y luego es reemplazado por un nuevo token, aquí está la solución completa (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'')
)
su proyecto urls.py (en la matriz 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)
En su configuración REST_FRAMEWORK, agregue ExpiringTokenAuthentication como una clase de autenticación en lugar de TokenAuthentication:
REST_FRAMEWORK = {
''DEFAULT_AUTHENTICATION_CLASSES'': (
''rest_framework.authentication.SessionAuthentication'',
#''rest_framework.authentication.TokenAuthentication'',
''yourmodule.authentication.ExpiringTokenAuthentication'',
),
}
Si observa que un token es como una cookie de sesión, puede mantener el tiempo de vida predeterminado de las cookies de sesión en Django: https://docs.djangoproject.com/en/1.4/ref/settings/#session-cookie-age .
No sé si Django Rest Framework maneja eso automáticamente, pero siempre puedes escribir un pequeño script que filtra los obsoletos y los marca como caducados.
Es una buena práctica que los clientes móviles renueven periódicamente su token de autenticación. Esto, por supuesto, depende del servidor para hacer cumplir.
La clase predeterminada TokenAuthentication no es compatible con esto, sin embargo, puede ampliarlo para lograr esta funcionalidad.
Por ejemplo:
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
También se requiere que se sobrescriba la vista de inicio de sesión del marco de trabajo predeterminado, de modo que el token se actualice cada vez que se inicie sesión:
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()
Y no te olvides de modificar las URL:
urlpatterns += patterns(
'''',
url(r''^users/login/?$'', ''<path_to_file>.obtain_expiring_auth_token''),
)