DRFでsimpleJWTを導入!ハッシュ化しないと正常に動かない!?

この記事は約 8 分で読めます。

みなさんこんにちは!ヒロポンです!

今日はDjangoRestFrameworkでSimpleJWTを使った認証をする場合、パスワードをハッシュ化しないと正常に動かない!

という問題について書いていこうと思います。

いやハッシュ化しないと正常に動かないのではなく、ハッシュ化して認証するのが通常なので、ハッシュ化していないと正常に動かないってニュアンスかなーー。。

まあいいや!本題

※結論を見たい方は「これで安心かと思いきや」って箇所を見てくれればと思います!

DRFで単純なSimpleJWTの導入は簡単だが

DjangoRestFrameworkでのSimpleJWTの導入は非常に簡単です。

まずは下記パッケージを導入

  • djangorestframework-simplejwt
  • djoser

です。

下記コマンドでインストールします。

pip install djangorestframework-simplejwt
pip install djoser

で、設定ファイルにいろいろ文言を追加します。

ここでいう設定ファイルとは、

django-admin startproject <プロジェクト名> .

で作成したプロジェクトファイル直下にある、setting.pyの中です。

まずはINSTALLED_APPSに下記を追加

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'core.apps.CoreConfig',
    'todo.apps.TodoConfig',
    'user.apps.UserConfig',
    'rest_framework',
    'djoser', # jwtの認証エンドポイントを追加するためのライブラリ
]

下記を追加

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        # デフォルトの認証をJWTにするSimple JWTの読み込み
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ],
}

JWTの設定を追加

# Jwt_tokenを使うときのおまじない
SIMPLE_JWT = {
    #トークンをJWTに設定
    'AUTH_HEADER_TYPES': ('JWT',),
    #トークンの有効時間の設定
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60)
}

これだけで終わります。

っで、次にやるのはルーティング。

直下のurls.pyを下記のようにします。

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/auth/', include('djoser.urls.jwt')),
]

別にapi/auth/の箇所はわかりやすいパスならなんでもよいと思います。

このパスを追加することで自動的に下記のパスが追加されます。

  • api/auth/jwt/create/ => トークン作成
  • api/auth/jwt/refresh/ => トークン再作成
  • api/auth/jwt/verify => トークン検証

ここで使用するのは「api/auth/jwt/create/」が主になるかと思います。

後は適当にモデルとシリアライザーとviewを作って、認証が無いとviewが見れないようにします。

TodoのModel、シリアライザー、View

Model、シリアライザー、Viewを用意するのがめんどくさい人ようにこちらで用意しますね!

まずはログインユーザーに紐づいたTodoModel

from django.db import models

# Todoはタイトル、完了フラグ、created,updatedを持っている。
class Todo(models.Model):
    title = models.CharField(max_length=100)
    done = models.BooleanField(default=False)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.title

次にシリアライザーです。

from rest_framework import serializers
from core.models import Todo


class TodoSeriarizer(serializers.ModelSerializer):

    created = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S', read_only=True)
    updated = serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S', read_only=True)

    class Meta:
        model = Todo
        fields = ['id', 'done', 'title', 'created', 'updated']

最後にViewを作ります。

from core.models import Todo
from rest_framework import viewsets,permissions,generics
from .serializers import TodoSeriarizer


class TodoViewSet(viewsets.ModelViewSet):
    queryset = Todo.objects.all()
    serializer_class = TodoSeriarizer
    permission_classes = (permissions.IsAuthenticated,)

ここで味噌なのは下記の部分です。

permission_classes = (permissions.IsAuthenticated,)

この文言でこのViewはトークン認証がされていないと見れないようになります。

マイグレーションしてみる。

では下記コマンドでマイグレーションしましょう

python manage.py makemigrations
python manage.py migrate

次にsuperuserを作ります。

python manage.py createsuperuser

作成したユーザーでログインしてみます。

python manage.py runserver

でサーバーを走らせて、api/auth/jwt/createにアクセスすると下記のような画面が出ます。

この画面で先ほど作成したsuperuserでログインします。

このようなトークンが返ってきます。ここで使用するのはaccessトークン。

ChromeのModHeaderとかのプラグインでリクエストヘッダーにトークンを載せて、Todoにアクセスするとページが見れるはずです。

こんな感じで見れますが、リクエストヘッダーからトークンを抜くと下記のようになります。

トークンを抜いて

再度Todoのurlにアクセスする。

認証されました!

これで安心かと思いきや

では次にユーザー登録した後ログインすると仮定して、UserCreate用のページからユーザーを作成しログインしてみましょう。

userのモデルはDjangoが用意してくれているものをそのまま使うとして、シリアライザーとViewを用意します。

from django.contrib.auth.models import User
from rest_framework.authtoken.models import Token
from rest_framework import serializers
from django.contrib.auth import get_user_model


class UserSerializer(serializers.ModelSerializer):

    class Meta:
        model = User
        fields = ['id', 'username', 'password']
        extra_kwargs = {'password': {'write_only': True}}
from django.shortcuts import render
from django.contrib.auth.models import User
from rest_framework import viewsets, generics, authentication, permissions
from rest_framework.exceptions import NotFound
from .serializers import UserSerializer


class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    permission_classes = (permissions.IsAuthenticated,)

作ったら適当にルーティング追加します。

っで、登録してみます。

こんな感じでユーザーを登録

ちゃんと登録できた

なぜかユーザーが見つからないとエラーがでます。

adminのページで登録ができているか確認する

adminページにユーザーが登録されていることを確認します。。

登録されています。

パスワードハッシュ化すると解消される

はい前置きが長くなりましたが、今回ログインできなかった理由。

それはパスワードハッシュ化していないことが原因です。

createsuperuserで作成したユーザーをadminページで見てみるとパスワードがハッシュ化されていることが分かります。

ですが、userパスから登録したユーザーはパスワードハッシュ化されていません。

結論。これが原因です。

パスワードハッシュ化して登録するロジックはシリアライザーに書く

では、パスワードハッシュ化はどのようにするのか?

下記のようにします。

from django.contrib.auth.models import User
from rest_framework.authtoken.models import Token
from rest_framework import serializers
from django.contrib.auth import get_user_model
from django.contrib.auth.hashers import make_password


class UserSerializer(serializers.ModelSerializer):

    # パスワードのハッシュ化
    # この文言でハッシュ化しないと、認証に通らない。
    def validate_password(self, value: str) -> str:
        return make_password(value)

    class Meta:
        model = User
        fields = ['id', 'username', 'password']
        extra_kwargs = {'password': {'write_only': True}}


これでuserパスでユーザーを作成した後、そのままログインしてトークンを取得できるはずです。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA