LoginSignup
53
58

More than 5 years have passed since last update.

Djangoで独自のトークン認証を実装する

Last updated at Posted at 2018-09-07

はじめに

WebアプリをSPAで実装したときにサーバーとのデータのやり取りはWebAPIになり、JSONでのデータの受け渡しが主だと思います。
Djangoでサーバーを構築するとWebAPIもとても簡単に実装することができます。
Webアプリにログイン機能などをもたせたい場合、その管理方法に思慮することになりますが、DjangoのRestFrameworkを使用すれば、認証などはデフォルトで色々と用意されています。

今回実現したいのは以下のような機能です。

  • メールアドレスとパスワードによるログイン
  • ユーザ情報はDjangoのUserモデルを使用する
  • 無操作によるタイムアウトをサーバー側で管理する
  • 同一ユーザーの重複ログインは不可とする(後勝ち)

Webアプリではよくあるような仕様ですが、これをDjangoのWebAPIではどのように実装すればよいのかを考えました。
同一ユーザーの重複ログインは先勝ちが理想ですが、経験上それは難しいので後勝ちとします。
TokenAuthencationの拡張でも実装は可能だと思いますが、勉強も兼ねてAuthenticationを独自に実装することにしました。

Python、Djangoともに経験が1ヶ月程度ですので、問題などはご指摘いただけると非常に助かります。

検証環境

OS:Windows7 Professional
Pythonがすでにインストールされていて、パスが登録されていること。
Macの場合はコマンドなどを少々読み替えてください。ソースコードは同じで問題ありません。

$ python --version
Python 3.6.6

仮想環境管理にvevnを使用していますが、必須ではありません。使用しない場合はvenvの記載は無視して問題ありません。

Djangoプロジェクトの作成と設定

プロジェクトの作成

venvでプロジェクト管理したいので、プロジェクトフォルダを作成して、仮想環境を作成。

プロジェクトフォルダ作成
$ mkdir example_auth

プロジェクトフォルダに移動
$ cd example_auth

仮想環境作成(venvを使用する場合)
$ python -m venv venv

仮想環境を有効化(venvを使用する場合)
$ venv\Scripts\activate

仮想環境ができたところで、djangoとrest-frameworkをインストールします。

djangoとrest-frameworkをインストール
$ pip install django djangorestframework
(省略)

インストールされたパッケージを確認
$ pip freeze
Django==2.1.1
djangorestframework==3.8.2
pytz==2018.5

djangoのプロジェクトを作成する。

$ django-admin.py startproject example_auth .

最後にピリオドを入力することで、カレントフォルダにプロジェクトを作成します。
プロジェクトを作成すると、以下のようなフォルダ構成になります。

example_auth
├ manage.py
├ example_auth
│ ├ __init__.py
│ ├ settings.py
│ ├ urls.py
│ └ wsgi.py
└ venv
   └ (割愛)

プロジェクトの作成が完了しました。

データベース作成

本記事ではデフォルトのSQLiteを使用します。

$ manage.py migrate
(省略)

カレントフォルダにdb.sqlite3というファイルが作成されます。

スーパーユーザーの作成

以下のコマンドでスーパーユーザーを作成します。

$ manage.py createsuperuser
Username (leave blank to use 'admin'): admin
Email address: admin@example.com
Password: admin
Password (again): admin
(省略)
Superuser created successfully.

2回目のパスワードを入力したあとに「パスワードが簡単すぎる」と言われますが、無視して「y」と入力してください。
スーパーユーザーの設定値は任意ですが、上記の設定が行われた前提で、話を進めます。

言語とタイムゾーンの設定

example_authフォルダの中にあるsettings.pyの言語とタイムゾーンの設定を編集します。

example_auth/settings.py
LANGUAGE_CODE = 'ja'                       # en-us → ja

TIME_ZONE = 'Asia/Tokyo'                   # UTC → Asia/Tokyo

Webサーバーの起動

以下のコマンドでWebサーバーを起動します。

$ manage.py runserver
Performing system checks...

System check identified no issues (0 silenced).
September 05, 2018 - 12:59:10
Django version 2.1.1, using settings 'example_auth.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.

ブラウザから「http://127.0.0.1:8000/」にアクセスしてWebサーバーが起動していることを確認します。

また「http://127.0.0.1/admin/」にアクセスして、管理画面に先ほど作成したスーパーユーザーでログインできることを確認します。

WebAPI作成の準備

アプリケーションの作成

今回は通常のWebサーバーが扱うHtmlではなく、WebAPIでJSONの送受信を行うアプリケーションを生成します。アプリケーション名は「api」とします。
以下のコマンドでアプリケーションを作成します。

$ manage.py startapp api

以下のフォルダとファイルが作成されます。

api
├ __init__.py
├ admin.py
├ apps.py
├ admin.py
├ models.py
├ tests.py
├ views.py
└ migrations
   └ __init__.py

作成したアプリケーションをsettings.pyに登録します。

example_auth/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'api',                          # 追加
]

モデルの作成

アプリケーションを作成する準備が整ったので、まずトークンを管理するクラスを作成します。トークンはユーザーごとに管理するため、このクラスはユーザーと紐付かせます。

api/models.py
from django.db import models
from django.contrib.auth.models import User

class ExampleToken(models.Model):
    # 紐付くユーザー
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    # アクセストークン
    token = models.CharField(max_length=40)
    # アクセス日時
    access_datetime = models.DateTimeField()

    def __str__(self):
        # メールアドレスとアクセス日時、トークンが見えるようにする
        dt = timezone.localtime(self.access_datetime).strftime("%Y/%m/%d %H:%M:%S")
        return self.user.email + '(' + dt + ') - ' + self.token

クラスのメンバ変数は以下の用途で使用します。

user : トークンに紐づくユーザー。ユーザーとは1:1の関係とします。
token : アクセストークン。ログイン後はこのトークンを使用してユーザーの認証を行います。max_lengthが40に設定されている理由は、トークンはsha1でハッシュ化した文字列を設定するためです。
access_datetime : 最後にアクセスした日時を格納します。サーバーにアクセスするたびに更新します。 

次に作成したモデルをデータベースに反映します。

$ manage.py makemigrations
(省略)

$ manage.py migrate
(省略)

モデルを管理画面に追加

作成したモデルを管理画面で参照できるようにadmin.pyも編集します。

api/admin.py
from django.contrib import admin
from .models import ExampleToken

class ExampleTokenAdmin(admin.ModelAdmin):
    fields = ['user', 'token', 'access_datetime',]

admin.site.register(ExampleToken, ExampleTokenAdmin)

設定が出来たらブラウザから設定画面にアクセスして、ExampleTokenモデルが追加されていることを確認します。データベースにはまだレコードが出来ていないので、中身は空っぽです。

ログイン機能の作成

データベースが完成したので、次にログイン機能を作成します。
ログイン機能の仕様としては、メールアドレスとパスワードがJSON形式でポストされ、一致するユーザーがいた場合はExampleTokenにレコードを追加してトークンを返却します。一致するユーザーがいない場合はエラーを返却します。

WebAPIでの実装となるため、rest-frameworkをsettings.pyに追加します。

example_token.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',               # 追加
    'api',
]

ログイン処理の作成

ログインしたらExampleTokenにレコードを追加しますので、まずその処理を作成します。
実装方法は色々あると思いますが、個人的な趣味でExampleTokenクラスの静的メソッドとして作成します。models.pyを以下のように編集してください。

api/models.py
from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone              # 追加

import hashlib                                 # 追加

class ExampleToken(models.Model):

    # このメソッドを追加
    @staticmethod
    def create(user: User):
        # ユーザの既存のトークンを取得
        if ExampleToken.objects.filter(user=user).exists():
            # トークンが既に存在している場合は削除する
            ExampleToken.objects.get(user=user).delete()

        # トークン生成(メールアドレス + パスワード + システム日付のハッシュ値とする)
        dt = timezone.now()
        str = user.email + user.password + dt.strftime('%Y%m%d%H%M%S%f')
        hash = hashlib.sha1(str.encode('utf-8')).hexdigest()    # utf-8でエンコードしないとエラーになる

        # トークンをデータベースに追加
        token = ExampleToken.objects.create(
            user = user,
            token = hash,
            access_datetime = dt)

        return token

importが追加になっているのと、createメソッドを新たに作成しました。
ユーザーを引数として呼び出し元から渡してもらい、そのユーザーに対するトークンを作成します。
トークンは使い捨てとして、ログイン時に新しく作成します。古いトークンがあった場合は削除します。
トークンは同一のものが存在すると困るので、一致しなそうな文字列をsha1でハッシュ化した文字列を使用しています。

作成したcreateメソッドを使用して、ログイン処理のWebAPIを作成します。views.pyを以下のように編集してください。

api/views.py
from django.contrib.auth.models import User
from django.http.response import JsonResponse
from rest_framework.views import APIView
from .models import ExampleToken

import json

class Login(APIView):
    def post(self, request, format=None):
        # リクエストボディのJSONを読み込み、メールアドレス、パスワードを取得
        try:
            data = json.loads(request.body)
            email = data['email']
            password = data['password']
        except:
            # JSONの読み込みに失敗
            return JsonResponse({'message': 'Post data injustice'}, status=400)

        # メールアドレスからユーザを取得
        if not User.objects.filter(email=email).exists():
            # 存在しない場合は403を返却
            return JsonResponse({'message': 'Login failure.'}, status=403)

        user = User.objects.get(email=email)

        # パスワードチェック
        if not user.check_password(password):
            # チェックエラー
            return JsonResponse({'message': 'Login failure.'}, status=403)

        # ログインOKの場合は、トークンを生成
        token = ExampleToken.create(user)

        # トークンを返却
        return JsonResponse({'token': token.token})

作成したWebAPIをアクセス可能とするため、urls.pyを編集します。

example_auth/urls.py
from django.contrib import admin
from django.urls import path, include         # includeを追加

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('api.urls')),        # 追加
]

apiフォルダにurls.pyを新たに作成し、以下の内容に編集してください。

api/urls.py
from django.urls import path
from .views import Login

urlpatterns = [
    path('login', Login.as_view(), name='login'),
]

これで「http://127.0.0.1:8000/api/login」にアクセスが可能になりました。
許容するメソッドはPOSTのみですので、普通にブラウザでアクセスしてもエラーとなります。
本記事でのWebAPIへのアクセスはcurlを使用します。(Windowsには標準で入っていないので、別途インストールが必要です。)

ログインAPIの実行

以下のコマンドで作成したログインAPIを実行します。
リクエストメソッドはPOSTで、POSTデータとしてメールアドレスとパスワードを送信します。

$ curl -i -X POST -d "{\"email\":\"admin@example.com\",\"password\":\"admin\"}" http://127.0.0.1:8000/api/login
HTTP/1.1 200 OK
Date: Wed, 05 Sep 2018 05:39:12 GMT
Server: WSGIServer/0.2 CPython/3.6.6
Content-Type: application/json
Vary: Accept, Cookie
Allow: POST, OPTIONS
X-Frame-Options: SAMEORIGIN
Content-Length: 53

{"token": "356dc257775c0fbf50bc0855c113de0065005f72"}

ステータスコードが200が返却され、トークンが取得できました。
この状態で管理画面でExampleTokenにレコードが追加されたことが確認できます。

もちろん、存在しないメールアドレスや、パスワードが間違っている場合は403エラーとなります。
今後はこのAPIで取得したトークンをサーバーに送信して認証を行います。

認証処理の作成

認証処理用のAPIを作成

認証処理を作成するために、まず簡単なAPIを作成します。
アクセスされたら「Yes」というメッセージを返すだけのイエスマンAPIです。以下のクラスをviews.pyに追加してください。

api/views.py
class YesMan(APIView):                              # YesManクラスを追加
    def post(self, request, format=None):
        return JsonResponse({'message': 'Yes'})

このAPIにアクセスするためにurls.pyを編集します。

api/urls.py
from django.urls import path
from .views import Login, YesMan           # YesManを追加

urlpatterns = [
    path('login', Login.as_view()),
    path('yesman', YesMan.as_view()),      # 追加
]

curlで追加したAPIにアクセスします。

$ curl -X POST http://127.0.0.1:8000/api/yesman
{"message": "Yes"}

当然ですが、このままでは認証はおろか、リクエストにトークンを含めなくても「Yes」と言われます。
では、認証されていないユーザには「Yes」と言われないようにします。
認証される条件は以下とします。

  • リクエストに正しいトークンが含まれること
  • トークンの最終アクセス日時から30分以内であること

上記のチェック処理をExampleTokenクラスに追加します。

api/models.py
from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone
from datetime import timedelta

import hashlib

class ExampleToken(models.Model):
    # 既存のコードは変更なし。以下のコードを追加

    @staticmethod
    def get(token_str: str):
        # 引数のトークン文字列が存在するかチェック
        if ExampleToken.objects.filter(token=token_str).exists():
            # 存在した場合はトークンを返却
            return ExampleToken.objects.get(token=token_str)
        else:
            # 存在しない場合はNoneを返却
            return None

    def check_valid_token(self):
        # このトークンが有効かどうかをチェック
        delta = timedelta(minutes=30)   # 有効時間は30分
        if(delta < timezone.now() - self.access_datetime):
            # 最終アクセス時間から30分以上経過している場合はFalseを返却
            return False

        return True

    def update_access_datetime(self):
        # 最終アクセス日時を現在日時で更新
        self.access_datetime = timezone.now()
        self.save()

追加したメソッドの使い方は以下のようなイメージです。

  1. クライアントからトークン文字列が送られてくるので、getメソッドを使用してトークン文字列からトークンクラスのインスタンスを取得。
  2. トークンの有効期限は最終アクセス時間から30分なので、check_valid_tokenメソッドで有効かどうかをチェック。
  3. トークンが有効だった場合は、update_access_datetimeメソッドを使用して、最終アクセス時間を現在日時で更新。

これらのメソッドを使用して、認証機能を作成します。

認証機能の作成

apiフォルダ内にauthentication.pyを新たに作成して、内容を以下のように編集します。

api/authentication.py
from django.contrib.auth.models import User
from rest_framework import authentication
from rest_framework import exceptions
from rest_framework import status
from .models import ExampleToken

class ExampleAuthentication(authentication.BaseAuthentication):
    def authenticate(self, request):
        # リクエストヘッダからトークン文字列を取得
        token_str = request.META.get('HTTP_X_AUTH_TOKEN')
        if not token_str:
            # リクエストヘッダにトークンが含まれない場合はエラー
            raise exceptions.AuthenticationFailed({'message': 'token injustice.'})

        # トークン文字列からトークンを取得する
        token = ExampleToken.get(token_str)
        if token == None:
            # トークンが取得できない場合はエラー
            raise exceptions.AuthenticationFailed({'message': 'Token not found.'})

        # トークンが取得できた場合は、有効期間をチェック
        if not token.check_valid_token():
            # 有効期限切れの場合はエラー
            raise exceptions.AuthenticationFailed({'message': 'Token expired.'})

        # トークンが有効な場合は、アクセス日時を更新
        token.update_access_datetime()

        return (token.user, None)

リクエストヘッダからトークン文字列を取得し、そのトークン文字列の整合性をチェックします。チェックNGの場合は、例外をスローして認証失敗としています。
認証機能を作成しただけではAPIには適応されないため、先ほど作成したイエスマンに認証機能を付与します。

api/views.py
from django.contrib.auth.models import User
from django.http.response import JsonResponse
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated       # 追加
from .models import ExampleToken
from .authentication import ExampleAuthentication            # 追加

import json

# Loginクラスは修正なし

class YesMan(APIView):
    authentication_classes = (ExampleAuthentication,)        # 追加
    permission_classes = (IsAuthenticated,)                  # 追加

    def post(self, request, format=None):
        return JsonResponse({'message': 'Yes'})

authentication_classesで使用する認証クラスを指定します。
permission_classesにはこのAPIを使用するための権限を設定します。IsAuthenticatedはauthentication_classesで設定した認証が行えた場合にこのAPIにアクセス可能となります。

この状態で、先ほどのcurlで送信したリクエストを、もう一度送信します。

$ curl -i -X POST http://127.0.0.1:8000/api/yesman
HTTP/1.1 403 Forbidden
Date: Wed, 05 Sep 2018 08:17:42 GMT
Server: WSGIServer/0.2 CPython/3.6.6
Content-Type: application/json
Vary: Accept
Allow: POST, OPTIONS
X-Frame-Options: SAMEORIGIN
Content-Length: 30

{"message":"token injustice."}

今度は認証機能が働いているため、トークン不正でステータスコード403が返却されました。
リクエストヘッダにトークンを含めて送信してみます。ここで送信するトークンは、ログインAPIで返却されたトークンです。

$ curl -i -X POST -H "X-AUTH-TOKEN:356dc257775c0fbf50bc0855c113de0065005f72" http://127.0.0.1:8000/api/yesman
HTTP/1.1 403 Forbidden
Date: Wed, 05 Sep 2018 08:19:52 GMT
Server: WSGIServer/0.2 CPython/3.6.6
Content-Type: application/json
Vary: Accept
Allow: POST, OPTIONS
X-Frame-Options: SAMEORIGIN
Content-Length: 28

{"message":"Token expired."}

先ほどログインした時間から30分以上経過している場合は、トークンの有効期限切れでエラーとなります。
こうなった場合は、再度ログインして新しいトークンを取得してからイエスマンを呼ぶと、正常に「Yes」と返事をしてくれます。

$ curl -X POST -d "{\"email\":\"admin@example.com\",\"password\":\"admin\"}" http://127.0.0.1:8000/api/login
{"token": "7348aa895d486360369652c1121538b900a819e3"}

C:\curl>curl -X POST -H "X-AUTH-TOKEN:7348aa895d486360369652c1121538b900a819e3" http://127.0.0.1:8000/api/yesman
{"message": "Yes"}

認証に成功したユーザー情報は、request.userで使用することが出来ます。
イエスマンAPIに認証成功したユーザーのメールアドレスもレスポンスに含めるには以下のようにします。

api/views.py
class YesMan(APIView):
    authentication_classes = (ExampleAuthentication,)
    permission_classes = (IsAuthenticated,)

    def post(self, request, format=None):
        return JsonResponse({'message': 'Yes', 'email': request.user.email})     # 修正

request.userにはExampleAuthenticationクラスのauthenticationメソッドが返却したユーザー情報が格納されます。

$ curl -X POST -H "X-AUTH-TOKEN:7348aa895d486360369652c1121538b900a819e3" http://127.0.0.1:8000/api/yesman
{"message": "Yes", "email": "admin@example.com"}

認証処理をデフォルト設定にする

現在の状態は、認証を行いたいAPIに対して、authentication_classesとpermission_classesを登録していますが、大規模なアプリケーションになると、すべてのAPIに対して登録するのは冗長なので、デフォルトで認証が行われるようにすることも出来ます。
setting.pyに以下のコードを追加します。

example_token/settings.py
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'api.authentication.ExampleAuthentication',
    ),
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    )
}

これでデフォルトで認証が行われるようになったので、API単位での認証設定を変更します。

api/views.py
from django.contrib.auth.models import User
from django.http.response import JsonResponse
from rest_framework.views import APIView
# from rest_framework.permissions import IsAuthenticated     # 削除
from .models import ExampleToken
# from .authentication import ExampleAuthentication          # 削除

import json

class Login(APIView):
    authentication_classes = ()                              # 追加
    permission_classes = ()                                  # 追加

    # 省略

class YesMan(APIView):
    # authentication_classes = (ExampleAuthentication,)      # 削除
    # permission_classes = (IsAuthenticated,)                # 削除

    def post(self, request, format=None):
        return JsonResponse({'message': 'Yes', 'email': request.user.email})

YesManクラスからは認証設定を削除しています。
Loginクラスには空の認証情報を追加していますが、ログイン時には認証は不要なので、デフォルトの認証を打ち消しています。

以上でWebAPIを使用した独自のトークン認証の実装は終了です。
BaseAuthenticationクラスを継承したクラスの作り次第で、どんな認証も可能だと思います。

WebAPIのテスト

以下のように、APITestCaseクラスを使用すると簡単にテストコードが作成できます。

api/test.py
from django.contrib.auth.models import User
from django.urls import reverse
from django.utils import timezone
from django.test import TestCase
from rest_framework import status
from rest_framework.test import APITestCase
from .models import ExampleToken
from datetime import timedelta
from time import sleep

import json

def create_user():
    admin = User.objects.create_superuser('admin', 'admin@example.com', 'admin')
    user = User.objects.create_user('test', 'test@example.com', 'test')
    return (admin, user)

def create_token():
    admin = User.objects.get(username='admin')
    ExampleToken.objects.create(
        user = admin,
        token = '1111111111222222222233333333334444444444',
        access_datetime = timezone.now()
    )
    test = User.objects.get(username='test')
    ExampleToken.objects.create(
        user = test,
        token = 'aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd',
        access_datetime = timezone.now() - timedelta(minutes=29.9)
    )

def create_token_expired():
    admin = User.objects.get(username='admin')
    ExampleToken.objects.create(
        user = admin,
        token = '1111111111222222222233333333334444444444',
        access_datetime = timezone.now() - timedelta(minutes=30)
    )

class ExampleTokenTests(TestCase):
    def test_create_token(self):
        (admin, user) = create_user()

        # adminのトークンを作成
        ExampleToken.create(admin)

        # トークンが作成されたかを確認
        self.assertTrue(ExampleToken.objects.filter(user=admin).exists(), 'Token not created.')

        # トークンの長さが40文字であるかを確認
        token = ExampleToken.objects.get(user=admin)
        self.assertEqual(len(token.token), 40, 'Token length not 40.')

        # トークン再作成でトークンが変更するかを確認
        token_old = token.token
        sleep(0.1)                  # 同一時刻だとトークンも同じなので、一瞬待機
        token = ExampleToken.create(admin)
        self.assertNotEqual(token_old, token.token)

class LoginTests(APITestCase):

    def test_login_success(self):
        create_user()

        # スーパーユーザーのログイン確認
        response = self.client.post(reverse('login'), {'email':'admin@example.com','password':'admin'}, format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK, json.loads(response.content))

        # 一般ユーザーのログイン確認
        response = self.client.post(reverse('login'), {'email':'test@example.com','password':'test'}, format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK, json.loads(response.content))

    def test_login_failure(self):
        create_user()

        # POSTデータなし(400エラー)
        response = self.client.post(reverse('login'))
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST, json.loads(response.content))

        # POSTデータにパスワードなし(400エラー)
        response = self.client.post(reverse('login'), {'email':'admin@example.com'}, format='json')
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST, json.loads(response.content))

        # POSTデータにメールアドレス無し(400エラー)
        response = self.client.post(reverse('login'), {'password':'admin'}, format='json')
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST, json.loads(response.content))

        # メールアドレス不正(403エラー)
        response = self.client.post(reverse('login'), {'email':'dammy@example.com','password':'admin'}, format='json')
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN, json.loads(response.content))

        # パスワード不正(403エラー)
        response = self.client.post(reverse('login'), {'email':'admin@example.com','password':'dummy'}, format='json')
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN, json.loads(response.content))

class YesManTests(APITestCase):
    def test_yesman_success(self):
        create_user()
        create_token()

        # トークン作成直後
        response = self.client.post(reverse('yesman'), HTTP_X_AUTH_TOKEN='1111111111222222222233333333334444444444')
        self.assertEqual(response.status_code, status.HTTP_200_OK, json.loads(response.content))

        # トークンの有効期限すれすれ
        response = self.client.post(reverse('yesman'), HTTP_X_AUTH_TOKEN='aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd')
        self.assertEqual(response.status_code, status.HTTP_200_OK, json.loads(response.content))

    def test_yesman_failure(self):
        create_user()
        create_token_expired()

        # トークンが含まれない(403エラー)
        response = self.client.post(reverse('yesman'))
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN, json.loads(response.content))

        # トークンが間違っている(403エラー)
        response = self.client.post(reverse('yesman'), HTTP_X_AUTH_TOKEN='aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd')
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN, json.loads(response.content))

        # トークンの有効期限が切れている(403エラー)
        response = self.client.post(reverse('yesman'), HTTP_X_AUTH_TOKEN='1111111111222222222233333333334444444444')
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN, json.loads(response.content))

ExampleTokenのテストは途中で面倒になって手抜きです。

53
58
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
53
58