为了方便解决跨域身份验证问题,本章我们在之前的项目中集成 Django Rest Framework 和 JWT 身份验证。
1. 集成 Django Rest Framework
1.1 安装
pip install djangorestframework
pip install pyjwt
然后在 settings.py的INSTALLED_APPS中添加rest_framework。
1.2 Class-Based Views
python
from rest_framework.views import APIView
from rest_framework.response import Response
class SnippetDetail(APIView):
def get(self, request): # 查找
...
return Response(...)
def post(self, request): # 创建
...
return Response(...)
def put(self, request, pk): # 修改
...
return Response(...)
def delete(self, request, pk): # 删除
...
return Response(...)
2. 集成JWT验证
2.1 安装
pip install djangorestframework-simplejwt
然后在settings.py中添加:
python
INSTALLED_APPS = [
...
'rest_framework_simplejwt',
...
]
REST_FRAMEWORK = {
...
'DEFAULT_AUTHENTICATION_CLASSES': (
...
'rest_framework_simplejwt.authentication.JWTAuthentication',
)
...
}
2.2 配置
在settings.py中添加:
python
from datetime import timedelta
...
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
'ROTATE_REFRESH_TOKENS': False,
'BLACKLIST_AFTER_ROTATION': False,
'UPDATE_LAST_LOGIN': False,
'ALGORITHM': 'HS256',
'SIGNING_KEY': SECRET_KEY,
'VERIFYING_KEY': None,
'AUDIENCE': None,
'ISSUER': None,
'JWK_URL': None,
'LEEWAY': 0,
'AUTH_HEADER_TYPES': ('Bearer',),
'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
'USER_ID_FIELD': 'id',
'USER_ID_CLAIM': 'user_id',
'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule',
'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
'TOKEN_TYPE_CLAIM': 'token_type',
'TOKEN_USER_CLASS': 'rest_framework_simplejwt.models.TokenUser',
'JTI_CLAIM': 'jti',
'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5),
'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),
}
2.3 添加获取jwt和刷新jwt的路由
python
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
)
urlpatterns = [
...
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
...
]
2.4 手动获取jwt
python
from rest_framework_simplejwt.tokens import RefreshToken
def get_tokens_for_user(user):
refresh = RefreshToken.for_user(user)
return {
'refresh': str(refresh),
'access': str(refresh.access_token),
}
2.5 将jwt验证集成到Django Channels中
python
"""General web socket middlewares
"""
from channels.db import database_sync_to_async
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser
from rest_framework_simplejwt.exceptions import InvalidToken, TokenError
from rest_framework_simplejwt.tokens import UntypedToken
from rest_framework_simplejwt.authentication import JWTTokenUserAuthentication
from channels.middleware import BaseMiddleware
from channels.auth import AuthMiddlewareStack
from django.db import close_old_connections
from urllib.parse import parse_qs
from jwt import decode as jwt_decode
from django.conf import settings
@database_sync_to_async
def get_user(validated_token):
try:
user = get_user_model().objects.get(id=validated_token["user_id"])
# return get_user_model().objects.get(id=toke_id)
return user
except:
return AnonymousUser()
class JwtAuthMiddleware(BaseMiddleware):
def __init__(self, inner):
self.inner = inner
async def __call__(self, scope, receive, send):
# Close old database connections to prevent usage of timed out connections
close_old_connections()
# Try to authenticate the user
try:
# Get the token
token = parse_qs(scope["query_string"].decode("utf8"))["token"][0]
# This will automatically validate the token and raise an error if token is invalid
UntypedToken(token)
except:
# Token is invalid
scope["user"] = AnonymousUser()
else:
# Then token is valid, decode it
decoded_data = jwt_decode(token, settings.SIMPLE_JWT["SIGNING_KEY"], algorithms=["HS256"])
# Get the user using ID
scope["user"] = await get_user(validated_token=decoded_data)
return await super().__call__(scope, receive, send)
def JwtAuthMiddlewareStack(inner):
return JwtAuthMiddleware(AuthMiddlewareStack(inner))
3. 配置Nginx
打开/etc/nginx/nginx.conf,在location /的路由中添加对PUT、POST、DELETE方法的支持。完整配置见【创建应用】界面。
bash
if ($request_method = 'POST') {
add_header 'Access-Control-Allow-Origin' 'https://www.acwing.com';
add_header 'Access-Control-Allow-Methods' 'GET, PUT, OPTIONS, POST, DELETE';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,X-Amz-Date';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
}
if ($request_method = 'DELETE') {
add_header 'Access-Control-Allow-Origin' 'https://www.acwing.com';
add_header 'Access-Control-Allow-Methods' 'GET, PUT, OPTIONS, POST, DELETE';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,X-Amz-Date';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
}
修改完后,执行:sudo nginx -s reload,使修改生效。