DRF JWT认证 #
一、JWT概述 #
1.1 什么是JWT #
JWT(JSON Web Token)是一种开放标准,用于在各方之间安全传输信息。JWT由三部分组成:
text
JWT结构
┌─────────────────────────────────────────────────────┐
│ Header.Payload.Signature │
├─────────────────────────────────────────────────────┤
│ Header: 算法和令牌类型 │
│ Payload: 用户数据和声明 │
│ Signature: 签名验证 │
└─────────────────────────────────────────────────────┘
1.2 JWT vs Token #
| 特性 | JWT | DRF Token |
|---|---|---|
| 存储 | 客户端 | 服务端数据库 |
| 扩展性 | 高 | 低 |
| 撤销 | 困难 | 容易 |
| 性能 | 高 | 需查询数据库 |
| 安全性 | 需要HTTPS | 需要HTTPS |
二、安装配置 #
2.1 安装simplejwt #
bash
pip install djangorestframework-simplejwt
2.2 配置settings.py #
python
INSTALLED_APPS = [
...
'rest_framework',
'rest_framework_simplejwt',
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
}
from datetime import timedelta
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60),
'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
'ROTATE_REFRESH_TOKENS': True,
'BLACKLIST_AFTER_ROTATION': True,
'ALGORITHM': 'HS256',
'SIGNING_KEY': SECRET_KEY,
'AUTH_HEADER_TYPES': ('Bearer',),
}
2.3 配置URL #
python
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
TokenVerifyView,
)
urlpatterns = [
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('api/token/verify/', TokenVerifyView.as_view(), name='token_verify'),
]
三、获取和使用Token #
3.1 获取Token #
bash
curl -X POST http://localhost:8000/api/token/ \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "password"}'
响应:
json
{
"access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}
3.2 使用Access Token #
bash
curl -X GET http://localhost:8000/api/articles/ \
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
3.3 刷新Token #
bash
curl -X POST http://localhost:8000/api/token/refresh/ \
-H "Content-Type: application/json" \
-d '{"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."}'
响应:
json
{
"access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}
四、自定义Token视图 #
4.1 自定义Token响应 #
python
from rest_framework_simplejwt.views import TokenObtainPairView
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
@classmethod
def get_token(cls, user):
token = super().get_token(user)
token['username'] = user.username
token['email'] = user.email
token['is_staff'] = user.is_staff
return token
def validate(self, attrs):
data = super().validate(attrs)
data['user_id'] = self.user.id
data['username'] = self.user.username
data['email'] = self.user.email
return data
class CustomTokenObtainPairView(TokenObtainPairView):
serializer_class = CustomTokenObtainPairSerializer
4.2 URL配置 #
python
urlpatterns = [
path('api/token/', CustomTokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]
4.3 响应示例 #
json
{
"access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"user_id": 1,
"username": "admin",
"email": "admin@example.com"
}
五、Token黑名单 #
5.1 启用黑名单 #
python
INSTALLED_APPS = [
...
'rest_framework_simplejwt.token_blacklist',
]
执行迁移:
bash
python manage.py migrate
5.2 配置 #
python
SIMPLE_JWT = {
...
'BLACKLIST_AFTER_ROTATION': True,
'BLACKLIST_APP': 'rest_framework_simplejwt.token_blacklist',
}
5.3 登出视图 #
python
from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
class LogoutView(APIView):
def post(self, request):
try:
refresh_token = request.data.get('refresh')
token = RefreshToken(refresh_token)
token.blacklist()
return Response({'message': '登出成功'}, status=status.HTTP_200_OK)
except Exception as e:
return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST)
六、保护视图 #
6.1 视图级别保护 #
python
from rest_framework_simplejwt.authentication import JWTAuthentication
from rest_framework.permissions import IsAuthenticated
class ArticleViewSet(viewsets.ModelViewSet):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
queryset = Article.objects.all()
serializer_class = ArticleSerializer
6.2 函数视图保护 #
python
from rest_framework.decorators import api_view, authentication_classes, permission_classes
from rest_framework_simplejwt.authentication import JWTAuthentication
from rest_framework.permissions import IsAuthenticated
@api_view(['GET'])
@authentication_classes([JWTAuthentication])
@permission_classes([IsAuthenticated])
def protected_view(request):
return Response({'message': f'Hello, {request.user.username}!'})
6.3 获取用户信息 #
python
from rest_framework_simplejwt.authentication import JWTAuthentication
class ProfileView(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def get(self, request):
serializer = UserSerializer(request.user)
return Response(serializer.data)
七、Token配置详解 #
7.1 完整配置 #
python
from datetime import timedelta
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60),
'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
'ROTATE_REFRESH_TOKENS': True,
'BLACKLIST_AFTER_ROTATION': True,
'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),
}
7.2 配置说明 #
| 配置项 | 说明 |
|---|---|
| ACCESS_TOKEN_LIFETIME | Access Token有效期 |
| REFRESH_TOKEN_LIFETIME | Refresh Token有效期 |
| ROTATE_REFRESH_TOKENS | 刷新时是否生成新Refresh Token |
| BLACKLIST_AFTER_ROTATION | 刷新后是否将旧Token加入黑名单 |
| ALGORITHM | 加密算法 |
| AUTH_HEADER_TYPES | 认证头类型 |
八、前端集成 #
8.1 登录获取Token #
javascript
async function login(username, password) {
const response = await fetch('/api/token/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ username, password }),
});
const data = await response.json();
localStorage.setItem('access_token', data.access);
localStorage.setItem('refresh_token', data.refresh);
return data;
}
8.2 使用Token请求 #
javascript
async function fetchArticles() {
const token = localStorage.getItem('access_token');
const response = await fetch('/api/articles/', {
headers: {
'Authorization': `Bearer ${token}`,
},
});
return response.json();
}
8.3 Token刷新 #
javascript
async function refreshToken() {
const refresh = localStorage.getItem('refresh_token');
const response = await fetch('/api/token/refresh/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ refresh }),
});
const data = await response.json();
localStorage.setItem('access_token', data.access);
return data;
}
8.4 Axios拦截器 #
javascript
import axios from 'axios';
const api = axios.create({
baseURL: '/api',
});
api.interceptors.request.use(config => {
const token = localStorage.getItem('access_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
api.interceptors.response.use(
response => response,
async error => {
if (error.response.status === 401) {
const newToken = await refreshToken();
error.config.headers.Authorization = `Bearer ${newToken.access}`;
return api.request(error.config);
}
return Promise.reject(error);
}
);
九、安全最佳实践 #
9.1 HTTPS #
JWT必须在HTTPS下使用:
python
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
9.2 Token存储 #
javascript
// 推荐:使用HttpOnly Cookie
// 不推荐:localStorage(易受XSS攻击)
// 如果必须使用localStorage
localStorage.setItem('access_token', token);
9.3 短期Token #
python
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=15),
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
}
9.4 密钥安全 #
python
import os
from datetime import timedelta
SIMPLE_JWT = {
'SIGNING_KEY': os.environ.get('JWT_SECRET_KEY'),
}
十、总结 #
本章学习了JWT认证:
- JWT基础:理解JWT结构和原理
- simplejwt配置:安装和配置JWT认证
- Token使用:获取、使用和刷新Token
- 自定义Token:添加自定义数据
- Token黑名单:实现登出功能
- 前端集成:与前端应用集成
让我们继续学习自定义认证!
最后更新:2026-03-28