मुख्य एपीआई के लिए टोकन प्रमाणीकरण: टोकन को समय-समय पर बदल दिया जाना चाहिए?


115

मैं Django और django- रेस्ट-फ्रेमवर्क के साथ एक RESTful API बना रहा हूं ।

प्रमाणीकरण तंत्र के रूप में हमने "टोकन प्रमाणीकरण" चुना है और मैंने इसे पहले ही Django-REST-Framework के प्रलेखन के बाद लागू किया है, सवाल यह है कि क्या आवेदन समय-समय पर टोकन को नवीनीकृत / परिवर्तित करना चाहिए और यदि हाँ? क्या यह मोबाइल ऐप होना चाहिए जिसके लिए टोकन को नवीनीकृत करना होगा या वेब-ऐप को स्वायत्तता से करना चाहिए?

सबसे अच्छा अभ्यास क्या है?

किसी को भी Django बाकी ढांचे के साथ अनुभव किया और एक तकनीकी समाधान का सुझाव दे सकता है?

(अंतिम प्रश्न की प्राथमिकता कम है)

जवाबों:


101

मोबाइल ग्राहकों को समय-समय पर अपने प्रमाणीकरण टोकन को नवीनीकृत करना अच्छा है। यह पाठ्यक्रम लागू करने के लिए सर्वर पर निर्भर है।

डिफ़ॉल्ट 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

डिफ़ॉल्ट रेस्ट फ्रेमवर्क लॉगिन दृश्य को ओवरराइड करना भी आवश्यक है, ताकि जब भी लॉगिन किया जाए, टोकन ताज़ा हो जाए:

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

6
यदि आप पुराने के लिए टाइमस्टैम्प को अपडेट करने के बजाय इसे समाप्त कर देते हैं, तो क्या आप ObtainExpiringAuthToken में एक नया टोकन बनाना चाहेंगे?
जोहर सेठ

4
एक नया टोकन बनाना समझ में आता है। आप मौजूदा टोकन कुंजी का मान पुनः प्राप्त कर सकते हैं और फिर आपको पुराने टोकन को हटाना नहीं पड़ेगा।
14

क्या होगा अगर मैं समाप्ति पर टोकन साफ़ करना चाहता हूं? जब मुझे फिर से एक नया टोकन प्राप्त होगा या टाइमस्टैम्प अपडेट हो जाएगा?
सोकॉन्का

3
इसके अलावा, आप सत्यापन को रोकने के बजाय एक क्रोनजॉब (सेलेरी बीट या समान) में समय-समय पर पुराने लोगों को निकालकर मेज से टोकन को समाप्त कर सकते हैं
BjornW

1
@BjornW मैं सिर्फ निष्कासन करूंगा और, मेरी राय में, यह अनुरोध करने के लिए एपीआई (या आपके सामने के छोर) के साथ एकीकृत व्यक्ति की जिम्मेदारी है, वे प्राप्त करते हैं, "अमान्य टोकन", और फिर ताज़ा हिट करें / नए टोकन एंडपॉइंट बनाएं
शिब्बीशाम

25

यदि कोई व्यक्ति उस समाधान के लिए इच्छुक है, लेकिन एक टोकन लेना चाहता है, जो एक निश्चित समय के लिए वैध है, तो यहां एक नया टोकन पूरा समाधान प्राप्त होता है (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 जोड़ें:

REST_FRAMEWORK = {

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

'ObtainExpiringAuthToken' object has no attribute 'serializer_class'जब मैं एपीआई समापन बिंदु तक पहुँचने की कोशिश कर रहा हूँ तो मुझे त्रुटि मिल रही है । निश्चित नहीं कि मैं क्या याद कर रहा हूं।
धर्मत

2
दिलचस्प समाधान, जिसका मैं बाद में परीक्षण करूंगा; फिलहाल आपकी पोस्ट ने मुझे सही रास्ते पर लाने में मदद की क्योंकि मैं केवल AUTHENTICATION_CLASSES सेट करना भूल गया था।
3

2
पार्टी में देर से आना लेकिन मुझे काम करने के लिए कुछ सूक्ष्म बदलाव करने की जरूरत थी। 1) utc_now = datetime.datetime.utcnow () utc_now = datetime.datetime.utcnow () प्रतिस्थापित करना चाहिए (tzinfo = pytz.UTC) 2) कक्षा में एक्सपोर्टरनोथेथेथेनेशन (TokenAuthentication): आपको मॉडल, सेल्फमॉडल = स्व की आवश्यकता है। get_model ()
इशान भट्ट

5

मैंने @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)

4

सोचा था कि मैं एक Django 2.0 जवाब DRY का उपयोग कर देना होगा। किसी ने पहले से ही हमारे लिए बनाया है, Google Django OAuth ToolKit। पिप के साथ उपलब्ध है, pip install django-oauth-toolkit। राउटर्स के साथ टोकन व्यूसेट्स जोड़ने के निर्देश: https://django-oauth-toolkit.readthedocs.io/en/latest/rest-framework/getting_started.html । यह आधिकारिक ट्यूटोरियल के समान है।

इसलिए मूल रूप से OAuth1.0 कल की सुरक्षा अधिक थी जो कि टोकननथेंटिकेशन है। फैंसी एक्सपायर टोकन पाने के लिए, OAuth2.0 इन दिनों सभी गुस्से में है। अनुमतियाँ ठीक करने के लिए आपको AccessToken, RefreshToken और स्कोप वैरिएबल मिलता है। आप इस तरह से क्रेडिट के साथ समाप्त होते हैं:

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

4

लेखक ने पूछा

प्रश्न यह है कि क्या आवेदन समय-समय पर टोकन को नवीनीकृत / परिवर्तित करना चाहिए और यदि हां तो कैसे? क्या यह मोबाइल ऐप होना चाहिए जिसके लिए टोकन को नवीनीकृत करना होगा या वेब-ऐप को स्वायत्तता से करना चाहिए?

लेकिन सभी उत्तर इस बारे में लिख रहे हैं कि टोकन को स्वचालित रूप से कैसे बदलना है।

मुझे लगता है कि टोकन द्वारा समय-समय पर टोकन बदलना व्यर्थ है। बाकी ढांचे में एक टोकन होता है जिसमें 40 अक्षर होते हैं, अगर हमलावर हर सेकंड 1000 टोकन का परीक्षण करता है, तो टोकन 16**40/1000/3600/24/365=4.6*10^7प्राप्त करने के लिए वर्षों की आवश्यकता होती है । आपको यह चिंता नहीं करनी चाहिए कि हमलावर एक-एक करके आपके टोकन का परीक्षण करेगा। यहां तक ​​कि आपने अपना टोकन बदल दिया है, आपके द्वारा अनुमान लगाए गए टोकन की संभावना समान है।

यदि आप चिंतित हैं कि हो सकता है कि हमलावर आपको टोकन दे सकते हैं, इसलिए आप समय-समय पर इसे बदलते हैं, हमलावर द्वारा टोकन प्राप्त करने के बाद, वह आपको टोकन बदल भी सकता है, जबकि वास्तविक उपयोगकर्ता को बाहर रखा गया है।

आपको वास्तव में क्या करना चाहिए थाहा हमलावर को अपने उपयोगकर्ता को टोकन प्राप्त करने से रोकने के लिए, https का उपयोग करें

वैसे, मैं सिर्फ यह कह रहा हूं कि टोकन को बदलना अर्थहीन है, उपयोगकर्ता नाम और पासवर्ड से टोकन कभी-कभी बदल जाता है। हो सकता है कि टोकन का उपयोग कुछ HTTP वातावरण में किया गया हो (आपको हमेशा इस तरह की स्थिति से बचना चाहिए) या किसी तीसरे पक्ष (इस मामले में, आपको अलग तरह का टोकन बनाना चाहिए, oauth2 का उपयोग करें) और जब उपयोगकर्ता कुछ खतरनाक काम कर रहा हो जैसे कि बदलना बाइंडिंग मेलबॉक्स या खाता हटाएं, आपको यह सुनिश्चित करना चाहिए कि आप मूल टोकन का उपयोग नहीं करेंगे क्योंकि यह स्निफर या टीसीपीडंप टूल्स का उपयोग करके हमलावर द्वारा प्रकट किया गया है।


हां, सहमत हैं, आपको कुछ अन्य माध्यमों (एक पुराने एक्सेस टोकन की तुलना में) द्वारा एक नया एक्सेस टोकन प्राप्त करना चाहिए। जैसे एक ताज़ा टोकन (या कम से कम पासवर्ड के साथ एक नया लॉगिन मजबूर करने का पुराना तरीका)।
BjornW

3

आप http://getblimp.github.io/django-rest-framework-jwt का लाभ उठा सकते हैं

यह लाइब्रेरी टोकन उत्पन्न करने में सक्षम है जिसकी समाप्ति तिथि है

डीआरएफ डिफ़ॉल्ट टोकन और डीआरएफ द्वारा टोकन प्रदान करने के बीच के अंतर को समझने के लिए एक नज़र डालें:

मल्टिपल वेबसर्वर के साथ Django REST JWT ऑथेंटिकेशन स्केल कैसे बनाएं?


1

यदि आप देखते हैं कि एक टोकन एक सत्र कुकी की तरह है, तो आप Django में सत्र कुकीज़ के डिफ़ॉल्ट जीवनकाल के लिए छड़ी कर सकते हैं: https://docs.djangoproject.com/en/1.4/ref/settings/#session-cookie-age

मुझे नहीं पता कि क्या Django रेस्ट फ्रेमवर्क स्वचालित रूप से हैंडल करता है लेकिन आप हमेशा एक छोटी स्क्रिप्ट लिख सकते हैं जो आउटडेटेड लोगों को फ़िल्टर करती है और उन्हें एक्सपायर्ड बताती है।


1
टोकन प्रमाणीकरण कुकीज़ का उपयोग नहीं करता है
s29

0

बस मैंने सोचा कि मैं इसे जोड़ दूंगा क्योंकि यह मेरे लिए मददगार था। मैं आमतौर पर जेडब्ल्यूटी पद्धति के साथ जाता हूं लेकिन कभी-कभी ऐसा कुछ बेहतर होता है। मैंने उचित आयात के साथ 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)

0

सिर्फ @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

इसके अलावा, इसे rest_framework.authentication.TokenAuthentication के बजाय DEFAULT_AUTHENTICATION_CLASSES में जोड़ना न भूलें

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.