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