Django REST framework でトークン認証をするメモ
トークンの作成と取得
まずは, INSTALLED_APPS
に rest_framework.authtoken
を追加しておく
settings.pyINSTALLED_APPS = ['django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles','rest_framework','rest_framework.authtoken', # <= 追加'api']REST_FRAMEWORK = {'DEFAULT_AUTHENTICATION_CLASSES': ['rest_framework.authentication.TokenAuthentication','rest_framework.authentication.SessionAuthentication',]}
これでトークン用モデルが定義されたので、マイグレーションを実行する
bash$ python manage.py makemigrations && python manage.py migrate
トークンを取得する API エンドポイントの作成
rest_framework.authtoken
にトークン取得用の view が定義されているのでルーティングをつけてあげる
pythonimport rest_framework.authtoken.views as auth_viewsurlpatterns = [path('admin/', admin.site.urls),path('api/', include(router.urls)),path('api-token-auth/', auth_views.obtain_auth_token) # <= 追加]
トークンの作成
引数に User モデルのインスタンスを渡して、普通に作れば OK
pythonfrom rest_framework.authtoken.models import TokenToken.objects.create(user=user # userは User モデルインスタンス)
ユーザーが作成されると同時にトークンも自動生成するようにすることが望ましいので、ユーザーモデルをカスタマイズして自動的にトークンを作るようにしておく
カスタムユーザーの定義
トークンと紐付けるカスタムユーザーを, AbstarctBaseUser
から定義していく
トークン認証に使うユーザーモデルは, settings.py
で
python# config/settings.pyAUTH_USER_MODEL = 'api.User'
と指定しておく必要がある
ただこうすると、管理ページへのログインもこの User を使うようになるので、 superuser 関連の設定もしておく
カスタムユーザーモデル
models.pyfrom django.contrib.auth.models import AbstractBaseUser, BaseUserManagerfrom django.contrib.auth.models import PermissionsMixinfrom django.utils.translation import gettext_lazy as _from django.db.models.signals import post_savefrom rest_framework.authtoken.models import Tokenfrom typing import Any, Optionalfrom django.conf import settings# ユーザーを作成した後に実行される@receiver(post_save, sender=settings.AUTH_USER_MODEL)def create_auth_token(sender: str, instance: Optional['User'] = None, created: bool = False, **kwargs: Any) -> None:if created and instance is not None:# トークンの作成と紐付けToken.objects.create(user=instance)class UserManager(BaseUserManager):def create_user(self, email: str, password: str) -> 'User':user = User(email=BaseUserManager.normalize_email(email),)user.set_password(password)user.save(using=self._db)# トークンの作成Token.objects.create(user=user)return userdef create_superuser(self, email: str, password: str) -> 'User':u = self.create_user(email=email,password=password)u.is_staff = Trueu.is_superuser = Trueu.save(using=self._db)return uclass User(AbstractBaseUser, PermissionsMixin):email = models.EmailField(unique=True,blank=False)password = models.CharField(_('password'),max_length=128)is_staff = models.BooleanField(default=False)is_superuser = models.BooleanField(default=False)EMAIL_FIELD = 'email'USERNAME_FIELD = 'email'REQUIRED_FIELDS = []objects = UserManager()
あとは普通に、serializer
と view
とルーティングを書く
serializers.pyfrom api.models import Userclass UserSerializer(serializers.ModelSerializer):class Meta:model = Userfields = ('pk', 'email', 'password')extra_kwargs = {'password': {'write_only': True}}def create(self, validated_data):return User.create_user(email=validated_data['email'],password=validated_data['password'])
views.pyfrom api.serializers import UserSerializerclass UserViewSet(viewsets.ModelViewSet):queryset = User.objects.all()serializer_class = UserSerializer
urls.pyfrom rest_framework import routersfrom .views import UserViewSetrouter = routers.DefaultRouter()router.register('users', UserViewSet)
これで完成
マイグレーションすれば、
bash$ python manage.py makemigrations && python manage.py migrate
また、既存のユーザーには
bash$ python manage.py shellIn [1]: from api.models import UserIn [2]: from rest_framework.authtoken.models import TokenIn [3]: for user in User.objects.all():try:Token.objects.create(user=user)except Exception:pass
こんな感じで付与できる
トークンの取得
開発サーバーを建てた状態で, httpie で API を叩いてみる
bashHTTP/1.1 200 OKAllow: POST, OPTIONSContent-Length: 52Content-Type: application/jsonDate: Mon, 25 May 2020 03:55:24 GMTServer: WSGIServer/0.2 CPython/3.7.2Vary: CookieX-Content-Type-Options: nosniffX-Frame-Options: DENY{"token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}
ここで、username
は、User モデルにて USERNAME_FIELD
に規定したもの
pythonclass User(AbstractBaseUser, PermissionsMixin):...USERNAME_FIELD = 'email'...
トークン認証
まだトークン認証はできるようになったが、各エンドポイントには認証なしでアクセスできるという状態なので、パーミッションクラスを書き換えておく
settings.pyREST_FRAMEWORK = {'DEFAULT_AUTHENTICATION_CLASSES': ['rest_framework.authentication.TokenAuthentication','rest_framework.authentication.SessionAuthentication'],'DEFAULT_PERMISSION_CLASSES': ['rest_framework.permissions.IsAuthenticated',],}
基本的にはトークン認証だけで良いが、DRF では各エンドポイントにアクセスしたときに API リファレンス(っぽいもの)が見れるので、セッションベースの認証を追加しておくとフロントエンド開発がしやすい(API の各エンドポイントをブラウザで叩くことで実際のレスポンスが見られる)
これで, 全ての view
のパーミッションがデフォルトが認証済みユーザー指定になった
認証いらずの View
は、
pythonfrom rest_framework.permissions import AllowAnyclass HogeViewSet(viewsets.ModelViewSet):permission_classes = [AllowAny]
のように各ビューで上書きできる