Django の JWT ライブラリと SimpleJWT ライブラリの使用
JWT
JWTの概要
JWT (JSON Web Token) は、Web アプリケーションまたはサービス間でクレームを渡すための、JSON 形式標準に基づく軽量の認証および認可メカニズムです。
JWT公式サイト:https://jwt.io/
特徴:
无状态:JWT 在服务器端不保存任何信息,因此可以跨多个请求进行身份验证。
自包含:JWT 包含了所有必要的信息,这样不需要去查询数据库或其他存储设施来验证用户身份。
可扩展性:由于 JWT 是基于 JSON 格式的标准,因此可以很容易地扩展以添加自定义数据。
强安全性:JWT 使用数字签名来验证消息的完整性和真实性,这样可以确保消息没有被篡改。
跨域支持:由于 JWT 是通过 HTTP 头部传输的,因此可以轻松地在跨域场景中使用。
主な用途:
JWT は、認証と認可によく使用されます。ユーザーがログインに成功すると、サーバーは JWT を生成し、クライアントに返します。次に、クライアントはそのトークンを後続のすべてのリクエストの Authorization ヘッダーに入れて、認証されていることを証明します。サーバーは秘密キーを使用して署名を検証し、ユーザー ID や権限などの必要な情報を JWT から抽出します。
JWT 構成:
JWT は、ヘッダー、ペイロード、署名の 3 つの部分で構成されます。
1.头部包含关于生成 JWT的算法和类型的元数据。
2.载荷是JWT的主体内容,它包含有关用户或其他实体的信息,例如其 ID 或角色。
3.签名是使用密钥对头部和载荷进行加密的字符串,以确保JWT 在传输过程中不被篡改。
.
で区切られた3 つの部分で構成される JWT トークン文字列
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
ヘッダ
JWT ヘッダーには次の 2 つの情報が含まれます。
声明类型,这里是jwt
声明加密的算法 通常直接使用HMAC SHA256
{
"alg": "HS256",
"typ": "JWT"
}
最初の部分は、base64 によるヘッダーの暗号化です。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
ペイロード
ペイロードは、有効な情報が保存される場所です。有効な情報は 3 つの部分で構成されます
1. 標準に登録されているステートメント (推奨されていますが、必須ではありません):
iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
2. 公的声明
公開声明には任意の情報を追加できます。一般的には、ユーザー関連の情報や、企業が必要とするその他の必要な情報です。ただし、この部分はクライアント側で復号化される可能性があるため、機密情報を追加することはお勧めしません。
3. 私的な声明
プライベート ステートメントは、プロバイダーとコンシューマによって共同で定義されたステートメントです。base64 は対称的に復号化され、情報のこの部分が平文情報として分類される可能性があるため、機密情報を保存することは通常推奨されません。
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1656239122
}
JWT の 2 番目の部分を取得するには、base64 で暗号化します。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
サイン
JWT の 3 番目の部分は署名メッセージであり、JWT トークンの偽造を防ぐために使用されます。
署名生成プロセス:
1.服务器在生成jwt token时,会将header和payload字符串进行拼接,用.隔开
2.使用一个只有服务器知道的密钥对拼接后的内容进行加密,加密之后生成的字符串就是signature内容
署名検証プロセス:
1.将客户端传递的jwt token中的header和payload字符串进行拼接,用.隔开
2.使用服务器自己的密钥对拼接之后的字符串进行加密
3.将加密之后的内容和将客户端传递的jwt token中signature进行对比,如果不一致,就说明jwt token是被伪造的
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
256-bit-secret
)
知らせ:
不要在jwt的payload部分存放敏感信息,客户端可解密得到
保护好secret密钥,使用https协议
Django は JWT 命令を使用します
django-rest-framework-jwt
JWT を使用して、Python プロジェクトでトークンを生成および検証します。これは、 または拡張機能を使用して実行できますdjangorestframework-simplejwt
。
django-rest-framework-jwt
-
GitHub アドレス:
https://github.com/jpadilla/django-rest-framework-jwt
-
ドキュメンテーション:
https://jpadilla.github.io/django-rest-framework-jwt/
djangorestframework-simplejwt
- GitHub アドレス:
https://github.com/jazzband/djangorestframework-simplejwt
- ドキュメンテーション:
https://django-rest-framework-simplejwt.readthedocs.io/en/latest/
注:django-rest-framework-jwt
現在はメンテナンスされておらず、低バージョンのフレームワークに適しています。最新バージョンの Django と DRF を使用している場合、JSON Web トークンを使用している場合、プロジェクトの開始時にエラーが報告されます。
ImportError: Could not import 'rest_framework_jwt.authentication.JSONWebTokenAuthentication' for API setting 'DEFAULT_AUTHENTICATION_CLASSES'. ImportError: cannot import name 'smart_text' from 'django.utils.encoding'
jwtライブラリの使用
依存ライブラリをインストールする
pipコマンドを使用して
djangorestframework-jwt
ライブラリをインストールする
pip install djangorestframework-jwt
settings.py ファイルを構成する
INSTALLED_APPS および REST_FRAMEWORK 構成に以下を追加します
INSTALLED_APPS = [
...
'rest_framework',
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
# 引入JWT认证机制,当客户端将jwt token传递给服务器之后
# 此认证机制会自动校验jwt token的有效性,无效会直接返回401(未认证错误)
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
),
}
# JWT扩展配置
JWT_AUTH = {
# 设置生成jwt token的有效时间
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
}
その他のオプションの構成項目:
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5), # 访问令牌的有效时间
'REFRESH_TOKEN_LIFETIME': timedelta(days=1), # 刷新令牌的有效时间
'ROTATE_REFRESH_TOKENS': False, # 若为True,则刷新后新的refresh_token有更新的有效时间
'BLACKLIST_AFTER_ROTATION': True, # 若为True,刷新后的token将添加到黑名单中,
# When True,'rest_framework_simplejwt.token_blacklist',should add to INSTALLED_APPS
'ALGORITHM': 'HS256', # 对称算法:HS256 HS384 HS512 非对称算法:RSA
'SIGNING_KEY': SECRET_KEY,
'VERIFYING_KEY': None, # if signing_key, verifying_key will be ignore.
'AUDIENCE': None,
'ISSUER': None,
'AUTH_HEADER_TYPES': ('Bearer',), # Authorization: Bearer <token>
'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION', # if HTTP_X_ACCESS_TOKEN, X_ACCESS_TOKEN: Bearer <token>
'USER_ID_FIELD': 'id', # 使用唯一不变的数据库字段,将包含在生成的令牌中以标识用户
'USER_ID_CLAIM': 'user_id',
# 'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',), # default: access
# 'TOKEN_TYPE_CLAIM': 'token_type', # 用于存储令牌唯一标识符的声明名称 value:'access','sliding','refresh'
#
# 'JTI_CLAIM': 'jti',
#
# 'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp', # 滑动令牌是既包含到期声明又包含刷新到期声明的令牌
# 'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5), # 只要滑动令牌的到期声明中的时间戳未通过,就可以用来证明身份验证
# 'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1), # path('token|refresh', TokenObtainSlidingView.as_view())
urls.py ファイルを構成する
urls.py ファイルを構成し、次のルーティング構成を追加します。
from rest_framework_jwt.views import obtain_jwt_token, refresh_jwt_token, verify_jwt_token
urlpatterns = [
path('api-token-auth/', obtain_jwt_token),
path('api-token-refresh/', refresh_jwt_token),
path('api-token-verify/', verify_jwt_token),
]
ビューの作成
JWTトークンを生成するカスタムビューを作成する
JWT 拡張機能は、JWT トークンを生成するメソッドを提供します。
from rest_framework_jwt.settings import api_settings
data = {
"name": "Django", "name": "20"}
payload = api_settings.JWT_PAYLOAD_HANDLER(user)
jwt_token = api_settings.JWT_ENCODE_HANDLER(payload)
from rest_framework_jwt.settings import api_settings
# 继承JSONWebTokenAPIView,使用CustomJWTSerializer进行序列化和验证
class CustomObtainJSONWebToken(JSONWebTokenAPIView):
serializer_class = CustomJWTSerializer
class CustomJWTSerializer(JSONWebTokenSerializer):
# validate 方法对提交的用户凭据进行验证,如果验证通过则生成 JWT token 并返回给客户端
def validate(self, attrs):
# 从请求中获取用户名和密码
credentials = {
self.username_field: attrs.get(self.username_field),
'password': attrs.get('password')
}
# 通过 Django 自带的 authenticate 函数进行认证
if all(credentials.values()):
user = authenticate(**credentials)
if user:
# 检查该用户是否处于活跃状态,如果被禁用则返回错误信息
if not user.is_active:
raise serializers.ValidationError('User account is disabled.')
# 生成JWT的payload(负载)
payload = api_settings.JWT_PAYLOAD_HANDLER(user)
# 生成JWT token
jwt_token = api_settings.JWT_ENCODE_HANDLER(payload)
response_data = {
'token': jwt_token,
}
# 将生成的 JWT token 返回给客户端
return response_data
else:
raise serializers.ValidationError('Unable to log in with provided credentials.')
else:
raise serializers.ValidationError('Must include "{username_field}" and "password".'.format(username_field=self.username_field))
権限の構成
ビューで @permission_classes デコレータを使用して、検証する必要がある権限を指定します。
from rest_framework.permissions import IsAuthenticated
class CustomView(APIView):
permission_classes = (IsAuthenticated,)
SimpleJWTライブラリの使用
SimpleJWT ライブラリをインストールする
pip コマンドを使用して SimpleJWT ライブラリをインストールします。
pip install djangorestframework-simplejwt
Django プロジェクトを構成する
次の設定を settings.py ファイルに追加します。
INSTALLED_APPS = [
'rest_framework',
'rest_framework_simplejwt',
]
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
# 将全局权限控制方案设置为仅允许认证用户访问
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
# JWT认证:使用SimpleJWT库提供的JWTAuthentication类来进行认证
'rest_framework_simplejwt.authentication.JWTAuthentication',
# sesssion认证
'rest_framework.authentication.SessionAuthentication',
# 基本认证
'rest_framework.authentication.BasicAuthentication',
),
}
SIMPLE_JWT = {
# token有效时长(返回的 access 有效时长)
'ACCESS_TOKEN_LIFETIME': datetime.timedelta(seconds=30),
# token刷新的有效时间(返回的 refresh 有效时长)
'REFRESH_TOKEN_LIFETIME': datetime.timedelta(seconds=20),
}
よりシンプルな JWT 構成リファレンス:https://django-rest-framework-simplejwt.readthedocs.io/en/latest/settings.html
ルーティングを構成する
プロジェクトレベルのルーティングを構成する
from django.urls import path, include, re_path
urlpatterns = [
path('admin/', include(('apps.admin.urls', 'admin'), namespace="admin")),
]
サブアプリケーションのJWTビューのルーティングを構成する
from django.urls import include, path
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView, TokenVerifyView
urlpatterns = [
path('login/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('verify/', TokenVerifyView.as_view(), name='token_verify'),
]
上記のビューのルーティングは JWT 独自のクラスを使用しており、ビューをカスタマイズして関連関数を実装することもできます。
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework_simplejwt.tokens import AccessToken, RefreshToken
from rest_framework.permissions import IsAuthenticated
class TokenObtainView(APIView):
# 视图需要被认证才能访问
permission_classes = [IsAuthenticated]
def post(self, request):
# 使用了SimpleJWT提供的AccessToken和RefreshToken类,为认证成功的用户生成对应的JWT令牌
access_token = AccessToken.for_user(request.user)
refresh_token = RefreshToken.for_user(request.user)
return Response({
'access_token': str(access_token),
'refresh_token': str(refresh_token),
})
from django.urls import path
from .views import TokenObtainView
urlpatterns = [
path('api/token/', TokenObtainView.as_view(), name='token_obtain'),
]
ユーザーを作成
Django 独自のユーザー認証システムを使用してログイン用のユーザーを作成する
import os
from django.test import TestCase
from django.contrib.auth.models import User
if not os.environ.get('DJANGO_SETTINGS_MODULE'):
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'meiduo_mall.settings')
import django
django.setup()
class MyTest(TestCase):
User.objects.create_user(username='admin', password='admin')
インターフェーステスト
1. ログインにアクセスします。
2. ログイン
ログイン インターフェイス、更新を返し、値にアクセスします。
refresh用来刷新获取新的Token值
access用来请求身份认证的Token
access的过期时间参照配置ACCESS_TOKEN_LIFETIME
refresh的过期时间参照配置REFRESH_TOKEN_LIFETIME
リフレッシュでリフレッシュした後のアクセスの有効期限は、現在のリフレッシュ時間 + ACCESS_TOKEN_LIFETIME となります。
3. トークン検証
ログイン インターフェイスから返されたアクセス値を使用して、トークン検証インターフェイスを呼び出します
。 4. トークンを更新します。
ログイン インターフェイスから返された更新値を使用して、トークン更新インターフェイスを呼び出します。
この有効期間の短いアクセス トークンの有効期限が切れると、有効期間の長いリフレッシュ トークンを使用して別のアクセス トークンを取得できます。
認証
返されたアクセス トークンを使用して、保護されたビューの認証を証明します。
path('test/', TestView.as_view(), name='test'),
from rest_framework.response import Response
from rest_framework.views import APIView
class TestView(APIView):
def get(self, request):
return Response("tet successfully")
リクエストヘッダーに認可を追加します。形式: Bearer [トークン値] (スペースあり)
カスタムトークンの要求
カスタム トークンでは、ビューでサブクラスを作成し、対応するシリアライザーのサブクラスを作成する必要があります。
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from rest_framework_simplejwt.views import TokenObtainPairView
class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
def validate(self, attrs):
data = super().validate(attrs)
refresh = self.get_token(self.user)
data['refresh'] = str(refresh)
data['access'] = str(refresh.access_token)
data['username'] = self.user.username
return data
class MyTokenObtainPairView(TokenObtainPairView):
serializer_class = MyTokenObtainPairSerializer
サブクラス化されたビューを指す URL ルーティングを構成する
re_path(r'^login/$', views.MyTokenObtainPairView.as_view()),
トークンを手動で作成する
ユーザー登録など、ユーザーのトークンを手動で作成し、登録後に直接トークンを返します。
class MyCreateTokenView(APIView):
permission_classes = [permissions.AllowAny]
def get(self, request, *args, **kwargs):
return Response("Get method is not supported !")
def post(self, request, *args, **kwargs):
refresh = RefreshToken.for_user(request.user)
content = {
'refresh': str(refresh),
'access': str(refresh.access_token),
}
return Response(content)
re_path(r'^register/$', views.MyCreateTokenView.as_view()),
SimpleJWTの応用
ユーザー認証とJWTトークン生成を実装する
from django.contrib.auth.models import User
from django.utils import timezone
from rest_framework import serializers
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from rest_framework_simplejwt.tokens import RefreshToken
class AuthSerializer(TokenObtainPairSerializer):
id = serializers.IntegerField(label='用户ID', read_only=True)
username = serializers.CharField(label='用户名')
token = serializers.CharField(label='Token', read_only=True)
refresh = serializers.CharField(label='Refresh', read_only=True)
# 从attrs参数中获取到传入的用户名和密码
def validate(self, attrs):
# 获取username和password
username = attrs['username']
password = attrs['password']
# 进行用户名和密码校验
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
raise serializers.ValidationError('用户名或密码错误.')
else:
# 校验密码
if not user.check_password(password):
raise serializers.ValidationError('用户名或密码错误')
# 给attrs中添加user属性,保存登录用户
attrs['user'] = user
return attrs
def create(self, validated_data):
# 获取登录用户user
user = validated_data['user']
# 设置最新登录时间
user.last_login = timezone.now()
user.save()
refresh = RefreshToken.for_user(user)
# 给user对象增加属性,保存jwt token的数据
user.refresh = str(refresh)
user.token = str(refresh.access_token)
return user
ログインの実装
AuthSerializer オブジェクトが作成され、POST リクエストのパラメーターが検証のために渡されます。検証に合格した場合は、serializer.save() メソッドを呼び出して JWT トークンを生成し、応答としてトークン データを返します。
from rest_framework import status
from rest_framework.generics import CreateAPIView
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from rest_framework.views import APIView
from apps.zd_admin.serializers.user import AuthSerializer
class LoginView1(APIView):
permission_classes = [AllowAny]
def post(self, request):
# 获取参数并进行校验
serializer = AuthSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
# 调用序列化器类的create方法,实现服务器签发jwt token
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
class LoginView(CreateAPIView):
permission_classes = [AllowAny]
serializer_class = AuthSerializer
urlpatterns = [
re_path(r'^login/$', views.LoginView.as_view()),
]
登録の実施
トークン認証情報を登録して返す登録インターフェイスにも同じことが当てはまります。
re_path(r'^register/$', views.RegisterView.as_view()),
class RegisterView(APIView):
permission_classes = [AllowAny]
def post(self, request):
user = request.data
username = user['username']
password = user['password']
# 保存注册数据
try:
user = User.objects.create_user(username=username, password=password)
except DatabaseError:
raise serializers.ValidationError('注册失败')
refresh = RefreshToken.for_user(user)
user = {
"username": user.username, "token": str(refresh.access_token), "refresh": str(refresh)}
return JsonResponse(user)
トークンの検証
ログインと登録で取得したトークンを使用してリクエストをテストします
re_path(r'^test/$', views.TestView.as_view()),
class TestView(APIView):
def get(self, request):
return Response("tet successfully")