はじめに
ある日現場でjwt認証を使用してログイン機能を実装する可能性が浮上した。
「認証ってなんでしょうか。。。。」
エンジニアとしてのスキルが低いもので恥ずかしながら認証について知りませんでした。
なんとか実装まで持っていくことができないかを自分なりにまとめてみたいと思います。
目次
- JWT認証とは
- DRFでの実装方法
1. JWT認証とは
・不正なアクセスでないか?ログインユーザが合っているか?などのチェックに用いられる。
・とても簡単に以下の図のようなイメージ
①ログイン情報を送るとサーバ上でログイン情報と照合する
②認証されるとトークンを返却する(以下サンプル)
・トークン
access
:アクセストークン
このアクセストークンをヘッダにつけることで認証を行うことができる(有効期限を持っている)
refersh
:リフレッシュトークン
アクセストークンよりも有効期限が長く設定されており、アクセストークンの有効期限が切れた場合にこちらを使用して認証を行う。
"refresh":
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTczMDAyMzQyMywiaWF0IjoxNzI5OTM3MDIzLCJqdGkiOiI3Y2U2NjI1Njg4Mzg0M2I1YTQ4YzY1NjVlNjZhZmJjYiIsInVzZXJfaWQiOiI1NmVlZDY3OS1mYTNlLTQ5NzUtYjIzMC1iYWQ5MTJmOTUxNjAifQ.TDj-MjAmT8NuySCCoSiAzcf0qERmlEUlKrGM9OhOyq8",
"access":
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzI5OTM3MzIzLCJpYXQiOjE3Mjk5MzcwMjMsImp0aSI6IjhhNTY5YzFmMTQyZTRhNmFhYjg1ZGMxMGRkNzNiYWY2IiwidXNlcl9pZCI6IjU2ZWVkNjc5LWZhM2UtNDk3NS1iMjMwLWJhZDkxMmY5NTE2MCJ9.IJjxz3QaMEbd86WmVwPI2AvBRku2_S-Wflal_rmLYkM"
トークンの中身
https://jwt.io/にアクセスすることでトークンの中身を確認できる
上記で送られたトークンの解析
③リクエスト時にトークンをヘッダに設定する
②で送られたトークンをヘッダに設定することでリクエスト時に毎回ログインのチェックをする必要をなくす。
④認証後リクエストに対するレスポンスを返却する
認証情報が含まれていないとリクエストを処理できない。(不正アクセス、管理者と一般ユーザの切り分けなど)
2. DRFでの実装方法
手順1 JWT認証を行うライブラリのインストール
以下のライブラリを使用します。
pip install "djangorestframework-simplejwt==5.2.*"
これはJWT認証を提供してくれるDRF向けの認証ライブラリ
上記の①でリクエストを送ったのもこの機能によるものである。
手順2 URLの追加
全体設定の方のurls.pyに以下を記載する。
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
swagger-ui
を使用していると以下のように表示される。
swagger-ui
のインストールは以下参照
認証に必要な項目は
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
認証に求められる項目は
以下のemail
とpassword
が対象となっている。
USERNAME_FIELD = "email"
PASSWORD_FIELD = "password"
これでログイン(認証機能)のベースが完成する。
おまけ
urls.py
に記載したTokenObtainPairView
がこの機能をになっているため、JWTトークンにその他情報を組み込みたい場合は、TokenObtainPairView
を継承したView、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
from rest_framework_simplejwt.views import TokenObtainPairView
from .serializers import CustomTokenObtainPairSerializer
class CustomTokenObtainPairView(TokenObtainPairView):
serializer_class = CustomTokenObtainPairSerializer
from django.urls import path
from .views import CustomTokenObtainPairView
urlpatterns = [
path('api/token/', CustomTokenObtainPairView.as_view(), name='token_obtain_pair'),
]
参考