Djangoと Django-rest-framework でRESTful APIを構築しています。
認証メカニズムとして「トークン認証」を選択し、Django-REST-Frameworkのドキュメントに従って既に実装しています。質問は、アプリケーションがトークンを定期的に更新/変更する必要があるかどうかです。トークンの更新を必要とするのはモバイルアプリですか、それともWebアプリが自律的にトークンを更新する必要がありますか?
ベストプラクティスは何ですか?
ここでDjango RESTフレームワークを経験し、技術的な解決策を提案できますか?
(最後の質問は優先度が低い)
モバイルクライアントに認証トークンを定期的に更新させることをお勧めします。もちろんこれは強制するサーバー次第です。
デフォルトの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()
そして、URLを変更することを忘れないでください:
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設定で、TokenAuthenticationの代わりにExpiringTokenAuthenticationを認証クラスとして追加します。
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
#'rest_framework.authentication.TokenAuthentication',
'yourmodule.authentication.ExpiringTokenAuthentication',
),
}
@odedfos answerを試しましたが、 誤解を招くエラーがありました 。これは同じ答えで、修正済みで適切にインポートされています。
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)
http://getblimp.github.io/Django-rest-framework-jwt を活用できます
このライブラリは、有効期限のあるトークンを生成できます
DRFデフォルトトークンとDRFが提供するトークンの違いを理解するには、以下をご覧ください。
DRYを使用してDjango 2.0の答えを出したと思います。誰かがこれをすでに構築してくれました。googleDjango OAuth ToolKit。pipで利用可能、pip install Django-oauth-toolkit
。ルーターにトークンビューセットを追加する手順: 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"
}
トークンがセッションCookieのようなものであることに気づいた場合、DjangoのセッションCookieのデフォルトの有効期間を守ることができます。 https://docs.djangoproject.com/en/1.4/ref/settings/#session- cookie-age 。
Django Rest Frameworkがそれを自動的に処理するかどうかはわかりませんが、古いスクリプトを除外して期限切れとしてマークする短いスクリプトをいつでも作成できます。
著者は尋ねた
問題は、アプリケーションがトークンを定期的に更新/変更すべきか、もしそうならどのようにすべきかです。トークンの更新を必要とするのはモバイルアプリですか、それともWebアプリが自律的にトークンを更新する必要がありますか?
しかし、すべての答えは、トークンを自動的に変更する方法について書いています。
トークンごとにトークンを定期的に変更することは無意味だと思います。残りのフレームワークは40文字のトークンを作成します。攻撃者が毎秒1000トークンをテストする場合、16**40/1000/3600/24/365=4.6*10^7
年、トークンを取得します。攻撃者がトークンを1つずつテストすることを心配しないでください。トークンを変更したとしても、トークンを推測する確率は同じです。
攻撃者がトークンを取得できるのではないかと心配している場合は、定期的に変更してください。攻撃者がトークンを取得した後、実際のユーザーが追い出される前にトークンを変更することもできます。
本当にすべきなのは、攻撃者がユーザーのトークンを取得できないようにすることですse https。
ちなみに、トークンごとにトークンを変更することは無意味で、ユーザー名とパスワードによってトークンを変更するのは意味のないことだと言っています。たぶん、トークンはいくつかのhttp環境(常にこの種の状況を避ける必要があります)またはいくつかのサードパーティ(この場合、oauth2を使用して異なる種類のトークンを作成する必要があります)で、ユーザーが変更などの危険なことをしている場合に使用されますメールボックスをバインドするか、アカウントを削除する場合、攻撃者がスニファーまたはtcpdumpツールを使用して明らかにした可能性があるため、Originトークンを使用しないようにする必要があります。
これが私にとって役立つので、私は私のものを追加すると思いました。私は通常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)