開発環境
- Python 3.9
- Django==3.2.12
- djangorestframework==3.13.1
困ったこと。
DjangoでModelSetViewを使ったAPIの開発で、
ログイン認証の必要なAPIのテストをしているとき。
class TestAuthApi(TestCase):
'''認証APIのテスト'''
AUTH_URL = '/api-token-auth/' # 認証用URL
fixtures = ['fixtures/test/test_data.json']
def test_token(self):
'''POSTメソッドのテスト'''
client = Client()
# トークン取得
token_response = client.post(
self.AUTH_URL,
data={
"username": "testuser",
"password": "password",
"email": "testuser@test.com",
},
content_type='application/json',
)
# トークン認証のチェック
self.assertEqual(token_response.status_code, 200)
self.assertTrue('token' in token_response.json())
をpython manage.py test
で実行すると、
AssertionError: 401 != 200
がraiseされた。
認証できていないということですね。
ちゃんと、ユーザー名も、パスワードも、メールアドレスも間違っていないことを確認しているので、なにが間違っているのだろう?
(python manage.py createsuperuser
で作ったユーザーでは、Webブラウザ、API共に認証できているので、User周りの設定が間違っていることはないだろう。)
関連するファイルの関連する箇所
from django.contrib.auth.models import AbstractUser
(略)
class User(AbstractUser):
pass
(略)
from rest_framework import serializers
from django.contrib.auth import get_user_model
(略)
class UserSerializer(serializers.ModelSerializer):
"""ユーザーシリアライザー"""
class Meta:
model = get_user_model()
fields = ('id', 'username', 'email')
(略)
from rest_framework.authtoken import views as auth_views
(略)
urlpatterns = [
(略)
# トークン認証用エンドポイント
url(r'^api-token-auth/', auth_views.obtain_auth_token),
]
(略)
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework.authtoken',
(略)
]
(略)
AUTH_USER_MODEL = 'apiv1.User'
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly',
),
'DEFAULT_AUTHENTICATION_CLASSES':[
'rest_framework.authentication.TokenAuthentication', # トークン認証
'rest_framework.authentication.SessionAuthentication', # セッション認証
]
}
(略)
[
{
"model": "アプリ名.user",
"pk": 1,
"fields": {
"username": "testuser",
"password": "password",
"email": "testuser@test.com",
(略)
}
},
]
解決策
「Webブラウザ、APIでは認証できているのに、テストClientでは認証できない。」
→ということは、テスト用データが間違っているのでは?
よくよく考えたら、テスト用データにパスワードの平文を書いたけど、DBに平文が保存されるはずないよね、、、
ということで調べると、Djangoではmake_password
関数を使って、パスワードをハッシュ化して保存している。
なので、fixtureのデータをパスワードのハッシュ値にすればよい。
ということで、ハッシュ値の取得を行う。
# python manage.py shell
>>> from django.contrib.auth.hashers import make_password
>>> make_password('password')
'pbkdf2_sha256$260000$O2CJugvRp7L8GMfcLfv8eZ$X8Mf8PPmtf37Emg2nD42bJ/m3PQF+jYVpgKEifiDbHo='
これを、fixtureに入れて、
[
{
"model": "アプリ名.user",
"pk": 1,
"fields": {
"username": "testuser",
"password": "pbkdf2_sha256$260000$O2CJugvRp7L8GMfcLfv8eZ$X8Mf8PPmtf37Emg2nD42bJ/m3PQF+jYVpgKEifiDbHo=",
"email": "testuser@test.com",
(略)
}
},
]
として、再度python manage.py test
を実行すると、
OK!!!
初歩的なミスでしたが、こういうのを地道に解決していく過程でフレームワークへの理解が深まったりしますよね。
(「テスト用のユーザー登録からやればいいじゃん」というよのは内緒。)
参考
https://medium.com/p/64a4b38904ff#9c63
(ちょっと古いけど。)