1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Django REST Framework】jwt認証とは - 初心者🔰

Posted at

はじめに

ある日現場でjwt認証を使用してログイン機能を実装する可能性が浮上した。

「認証ってなんでしょうか。。。。」

エンジニアとしてのスキルが低いもので恥ずかしながら認証について知りませんでした。

なんとか実装まで持っていくことができないかを自分なりにまとめてみたいと思います。

目次

  1. JWT認証とは
  2. DRFでの実装方法

1. JWT認証とは

・不正なアクセスでないか?ログインユーザが合っているか?などのチェックに用いられる。
・とても簡単に以下の図のようなイメージ
名称未設定-1.png

①ログイン情報を送るとサーバ上でログイン情報と照合する

以下のようなログインを想定した情報を送る(詳細は後述)
スクリーンショット 2024-10-26 19.18.51.png

②認証されるとトークンを返却する(以下サンプル)

・トークン
access:アクセストークン
このアクセストークンをヘッダにつけることで認証を行うことができる(有効期限を持っている)

refersh:リフレッシュトークン
アクセストークンよりも有効期限が長く設定されており、アクセストークンの有効期限が切れた場合にこちらを使用して認証を行う。

"refresh":
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTczMDAyMzQyMywiaWF0IjoxNzI5OTM3MDIzLCJqdGkiOiI3Y2U2NjI1Njg4Mzg0M2I1YTQ4YzY1NjVlNjZhZmJjYiIsInVzZXJfaWQiOiI1NmVlZDY3OS1mYTNlLTQ5NzUtYjIzMC1iYWQ5MTJmOTUxNjAifQ.TDj-MjAmT8NuySCCoSiAzcf0qERmlEUlKrGM9OhOyq8",
"access":
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzI5OTM3MzIzLCJpYXQiOjE3Mjk5MzcwMjMsImp0aSI6IjhhNTY5YzFmMTQyZTRhNmFhYjg1ZGMxMGRkNzNiYWY2IiwidXNlcl9pZCI6IjU2ZWVkNjc5LWZhM2UtNDk3NS1iMjMwLWJhZDkxMmY5NTE2MCJ9.IJjxz3QaMEbd86WmVwPI2AvBRku2_S-Wflal_rmLYkM"

トークンの中身
https://jwt.io/にアクセスすることでトークンの中身を確認できる
上記で送られたトークンの解析
スクリーンショット 2024-10-26 19.12.39.png

③リクエスト時にトークンをヘッダに設定する

②で送られたトークンをヘッダに設定することでリクエスト時に毎回ログインのチェックをする必要をなくす。

④認証後リクエストに対するレスポンスを返却する

認証情報が含まれていないとリクエストを処理できない。(不正アクセス、管理者と一般ユーザの切り分けなど)

2. DRFでの実装方法

手順1 JWT認証を行うライブラリのインストール

以下のライブラリを使用します。

pip install "djangorestframework-simplejwt==5.2.*"

これはJWT認証を提供してくれるDRF向けの認証ライブラリ
上記の①でリクエストを送ったのもこの機能によるものである。

手順2 URLの追加

全体設定の方のurls.pyに以下を記載する。

urls.py
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),

swagger-uiを使用していると以下のように表示される。
スクリーンショット 2024-10-26 20.01.58.png
swagger-uiのインストールは以下参照

認証に必要な項目は

user.py
import uuid
from django.contrib.auth.validators import UnicodeUsernameValidator
from django.contrib.auth.models import AbstractUser, UserManager as AbstractUserManager
from django_boost.models.mixins import LogicalDeletionMixin
from django.db import models


class UserManager(AbstractUserManager):
    def create_user(self, email, password=None, **extra_fields):
        if not email:
            raise ValueError("The Email field must be set")
        email = self.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=None, **extra_fields):
        extra_fields.setdefault("is_staff", True)
        extra_fields.setdefault("is_superuser", True)

        if extra_fields.get("is_staff") is not True:
            raise ValueError("Superuser must have is_staff=True.")
        if extra_fields.get("is_superuser") is not True:
            raise ValueError("Superuser must have is_superuser=True.")

        return self.create_user(email, password, **extra_fields)


class User(LogicalDeletionMixin, AbstractUser):
    username_validator = UnicodeUsernameValidator()

    class Role(models.IntegerChoices):
        MANAGEMENT = 0
        GENERAL = 1
        PART_TIME = 2

    # 不要なフィールドはNoneにすることができる
    first_name = None
    last_name = None
    date_joined = None
    groups = None
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    password = models.CharField("パスワード", max_length=100)
    username = models.CharField(
        max_length=150,
        unique=True,
        validators=[username_validator],
    )
    email = models.EmailField(max_length=254, unique=True)
    role = models.PositiveIntegerField(choices=Role.choices, default=Role.PART_TIME)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    # デフォルトはusernameだが今回は社員番号を指定
    USERNAME_FIELD = "email"
    PASSWORD_FIELD = "password"
    # uniqueのemailとusernameを指定
    REQUIRED_FIELDS = ["password"]

    objects = UserManager()

    class Meta:
        db_table = "users"

    def __str__(self):
        return self.pk

認証に求められる項目は
以下のemailpasswordが対象となっている。

    USERNAME_FIELD = "email"
    PASSWORD_FIELD = "password"

これでログイン(認証機能)のベースが完成する。

おまけ

urls.pyに記載したTokenObtainPairViewがこの機能をになっているため、JWTトークンにその他情報を組み込みたい場合は、TokenObtainPairViewを継承したView、Serializerを作成することで可能となる。

custom_serializer
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer

class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
    def validate(self, attrs):
        data = super().validate(attrs)
        data.update({'custom_info': '任意の情報'})  # 任意の情報を追加
        return data

custom_view
from rest_framework_simplejwt.views import TokenObtainPairView
from .serializers import CustomTokenObtainPairSerializer

class CustomTokenObtainPairView(TokenObtainPairView):
    serializer_class = CustomTokenObtainPairSerializer

urls.py
from django.urls import path
from .views import CustomTokenObtainPairView

urlpatterns = [
    path('api/token/', CustomTokenObtainPairView.as_view(), name='token_obtain_pair'),
]

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?