django-rest-framework-jwtの認証をカスタマイズする方法
スポンサーリンク
django-rest-framework-jwt については以前こちらの記事でご紹介しました。
django-rest-framework-jwt では認証APIを標準で用意してくれているので、通常はそれを使うことになります。
ですが、論理削除されたユーザーの場合は認証を失敗させたりだとか、認証処理をカスタマイズしたい場合は自前で認証してアクセストークンを返す処理を用意する必要があります。
本記事では、企業コード/メールアドレス/パスワードにて認証するケースを想定したカスタマイズ方法についてご紹介します。
Django および Django REST framework が初めてという方はこちらをどうぞ!
では、さっそく始めます。
プロジェクトを作成する
さくっと作成していきます。
$ django-admin startproject racchai $ cd racchai
settings.py にて INSTALLED_APPS
と DATABASES
を編集。
INSTALLED_APPS = ( 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'rest_framework', + 'rest_framework_jwt', + 'racchai', ) DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'racchai', 'USER': 'root', 'PASSWORD': 'root', 'HOST': 'localhost', } }
ついでにMySQLにデータベースを作成しておきましょう。
$ mysql -uroot -p -e "CREATE DATABASE racchai";
はい、以上です。
認証モデルを定義する
Django のユーザーテーブルには企業コードは存在しませんので、追加しておく必要があります。
ユーザーテーブルのカス高い図については、ここでは詳しい解説は割愛します。よくわからないという方はこちらをご参照ください。
今回は models.py を以下のように作成するだけでひとまずはOKです。
from django.db import models from django.contrib.auth.models import AbstractBaseUser, BaseUserManager class RacchaiUserManager(BaseUserManager): def create_user(self, email, password=None, **extra_fields): if not email: raise ValueError('Users must have a email address') email = RacchaiUserManager.normalize_email(email) user = self.model(email=email, **extra_fields) user.set_password(password) user.save(using=self._db) return user def create_superuser(self, email, password): return self.create_user(email, password) class RacchaiUser(AbstractBaseUser): email = models.EmailField(max_length=128, unique=True) company = models.CharField(max_length=128, null=True) USERNAME_FIELD = 'email' objects = RacchaiUserManager() class Meta: db_table = 'racchai_user' swappable = 'AUTH_USER_MODEL'
本当は ./manage.py createsuperuser
で企業コードも設定できるようにしたいところですが、今回はそこまではやりません。
RacchaiUserを作成したら、settings.py に以下を追記して認証モデルを差し替えておきます。
AUTH_USER_MODEL = 'racchai.RacchaiUser'
最後にマイグレーションを実行したら完了です。
$ ./manage.py migrate
念のためテーブル定義を確認。問題なさそうですね。
mysql> desc racchai_user; +------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+--------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | password | varchar(128) | NO | | NULL | | | last_login | datetime(6) | YES | | NULL | | | email | varchar(128) | NO | UNI | NULL | | | company | varchar(128) | NO | | NULL | | +------------+--------------+------+-----+---------+----------------+
Serializerを定義する
次はAPIの入力情報を扱うSerializerを定義します。今回は入力値が3つあるので、以下のようになります。
jwt_auth.py
from rest_framework import serializers class AuthInputSerializer(serializers.Serializer): company = serializers.CharField() email = serializers.EmailField() password = serializers.CharField()
View を定義する
最後に API 本体の実装です。さきほど Serializer を定義した jwt_auth.py に以下を追記しましょう。
from django.contrib.auth import authenticate from rest_framework_jwt.settings import api_settings from rest_framework.generics import GenericAPIView from rest_framework.exceptions import AuthenticationFailed from rest_framework.response import Response jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER class AuthView(GenericAPIView): serializer_class = AuthInputSerializer def post(self, request): serializer = AuthInputSerializer(data=request.data) serializer.is_valid(raise_exception=True) user = authenticate(**serializer.data) if not user: raise AuthenticationFailed() # 以下2行が今回のカスタマイズ部分 if user.company != serializer.data['company']: raise AuthenticationFailed() payload = jwt_payload_handler(user) return Response({ 'token': jwt_encode_handler(payload), })
最後に urls.py に以下を追記して /jwt_auth
でアクセスできるようにします。
from racchai.jwt_auth import AuthView urlpatterns = [ url(r'^jwt_auth', AuthView.as_view()), ]
これで認証ができるようになったはずです。
試してみる
API を試す前に、まだユーザーを登録していなかったので先にユーザーを作成しておきます。
$ ./manage.py createsuperuser Email: jwt@racchai.com Password: jwt Password (again): jwt Superuser created successfully.
前述の通り、この時点ではユーザーレコードに会社コードが設定されていませんので、SQL で直接会社コードを設定してしまいます。会社コードは仮に0001
としておきます。
$ mysql -uroot -p racchai -e "update racchai_user set company = '0001'"
準備が整ったところで、開発サーバーを起動して認証APIを実行してみます。
$ ./manage.py runserver .... $ curl http://localhost:8000/jwt_auth -d "email=jwt@racchai.com&password=jwt&company=0001" {"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Imp3dEByYWNjaGFpLmNvbSIsInVzZXJfaWQiOjIsImVtYWlsIjoiand0QHJhY2NoYWkuY29tIiwiZXhwIjoxNDYzNjI1NDQ5fQ.YX6a_9680xDDYOiqwXX1kAZA1dMH3rLIXq7oGTaOawc"}
無事JWTアクセストークンを取得できましたね!
念のため、会社コードを間違えたらどうなるのか見ておくことにします。
$ curl http://localhost:8888/jwt_auth -d "email=jwt@racchai.com&password=jwt&company=0002" {"detail":"Incorrect authentication credentials."}
ちゃんと認証に失敗することも確認できましたね。
まとめ
だいぶ早足でしたが、カスタマイズ自体は難しいことではないってことだけ伝われば幸いです!