JWT 认证中Bearer前缀处理问题解决方案
JWT 认证中Bearer前缀处理问题解决方案文档
一、问题背景
在使用 JWT(JSON Web Token)进行 API 认证时,前端通常会在Authorization请求头中添加Bearer前缀(如Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...),这是 RFC 6750 标准的要求。然而,部分后端框架(如 DRF SimpleJWT)在默认配置下可能无法正确处理Bearer前缀,导致认证失败(返回403 Forbidden)。本文档将系统解析问题原因,并提供健壮的解决方案。
二、核心问题分析
2.1 问题现象
前端按标准添加Bearer前缀后,后端返回403 Forbidden,但直接传递令牌(无Bearer前缀)时认证成功。
2.2 问题根源
DRF SimpleJWT 的默认认证类JWTAuthentication在提取令牌时,对Authorization头部的处理逻辑存在缺陷:
- 严格的空格检查:默认要求Authorization头部格式为Bearer <token>(仅一个空格),若存在多余空格(如Bearer token123)或令牌中包含空格(如被截断),会导致提取失败。
- 大小写敏感:仅识别Bearer(首字母大写),无法处理bearer、BeArEr等大小写混合的情况。
- 不兼容无前缀场景:若客户端错误省略Bearer前缀(非标准但常见),默认认证类会直接拒绝请求。
三、解决方案:自定义健壮认证类
通过自定义认证类,覆盖JWTAuthentication的get_raw_token方法,实现更灵活的Bearer前缀处理逻辑。
3.1 自定义认证类实现
# apps/users/authentication.py
import re
import logging
from rest_framework_simplejwt.authentication import JWTAuthentication
from rest_framework_simplejwt.exceptions import InvalidToken
logger = logging.getLogger(__name__)
class ProductionGradeJWTAuthentication(JWTAuthentication):
"""
生产级健壮 JWT 认证类,解决以下问题:
- 处理大小写混合的 `Bearer` 前缀(如 `bearer`、`BeArEr`)
- 容忍多余空格(如 ` Bearer token123 `)
- 兼容无前缀令牌(非标准但常见)
- 安全日志记录(避免泄露完整令牌)
"""
# 正则匹配 `Bearer` 前缀(不区分大小写,匹配至少一个空格)
BEARER_PATTERN = re.compile(r'^bearer\s+', re.IGNORECASE)
def get_raw_token(self, header):
"""
从 `Authorization` 头部提取原始令牌(去除 `Bearer ` 前缀)
"""
# 空值检查
if not header:
return None
# 清理头部:合并多余空格(如 " Bearer token123 " → "Bearer token123")
clean_header = ' '.join(header.strip().split())
# 移除 `Bearer` 前缀(不区分大小写)
token = self.BEARER_PATTERN.sub('', clean_header, count=1)
# 安全日志记录(仅记录令牌首尾部分,避免敏感信息泄露)
if token:
if len(token) > 50:
logger.debug(f"提取令牌: {token[:10]}...{token[-10:]}")
else:
logger.debug(f"提取令牌: [REDACTED]")
else:
logger.warning("`Authorization` 头部无有效令牌")
return token if token else None
3.2 配置启用自定义认证类
在 Django 项目的settings.py中,将默认认证类替换为自定义类:
# settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'apps.users.authentication.ProductionGradeJWTAuthentication', # 替换原认证类
],
# 其他 DRF 配置...
}
四、前端规范实现(以 Quasar + Axios 为例)
前端应严格遵循 RFC 6750 标准,在Authorization头部添加Bearer前缀:
// axios 拦截器配置(src/boot/axios.js)
import { boot } from 'quasar/wrappers'
import axios from 'axios'
// 创建 axios 实例
const api = axios.create({
baseURL: process.env.API_BASE_URL,
timeout: 5000
})
// 请求拦截器:自动添加 `Bearer ` 前缀
api.interceptors.request.use(config => {
const accessToken = localStorage.getItem('access_token')
if (accessToken) {
config.headers.Authorization = `Bearer ${accessToken}` // 标准格式
}
return config
}, error => {
return Promise.reject(error)
})
export default boot(({ app }) => {
app.config.globalProperties.$axios = api
})
五、测试验证(确保健壮性)
通过编写单元测试,验证自定义认证类能处理各种边界情况:
# tests/test_authentication.py
from django.test import TestCase
from apps.users.authentication import ProductionGradeJWTAuthentication
class ProductionGradeJWTAuthenticationTests(TestCase):
def setUp(self):
self.auth = ProductionGradeJWTAuthentication()
def test_standard_format(self):
"""标准格式:`Bearer <token>`"""
header = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
self.assertEqual(self.auth.get_raw_token(header), "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9")
def test_case_insensitive(self):
"""大小写混合:`beArEr <token>`"""
header = "beArEr eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
self.assertEqual(self.auth.get_raw_token(header), "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9")
def test_extra_spaces(self):
"""多余空格:` Bearer <token> `"""
header = " Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 "
self.assertEqual(self.auth.get_raw_token(header), "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9")
def test_no_prefix(self):
"""无前缀:直接传递令牌"""
header = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
self.assertEqual(self.auth.get_raw_token(header), "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9")
def test_empty_header(self):
"""空头部:返回 None"""
self.assertIsNone(self.auth.get_raw_token(""))
def test_invalid_format(self):
"""无效格式:仅 `Bearer` 无令牌"""
header = "Bearer"
self.assertIsNone(self.auth.get_raw_token(header))
六、生产环境注意事项
6.1 安全日志记录
自定义认证类中已添加安全日志(仅记录令牌首尾部分),避免敏感信息泄露。生产环境需确保日志系统不存储完整令牌。
6.2 速率限制
为防止暴力破解,建议为认证接口添加速率限制(如使用django-ratelimit):
# settings.py
RATELIMIT_VIEW = 'apps.users.views.rate_limit_response'
RATELIMIT_ENABLE = True
RATELIMIT_KEY = 'user' # 按用户限制
6.3 令牌过期处理
若令牌过期,前端应使用刷新令牌(Refresh Token)调用/api/users/token/refresh/获取新令牌,避免频繁登录。
七、最佳实践总结
|
主体 |
行动 |
理由 |
|
前端 |
严格添加Bearer前缀 |
符合 RFC 6750 标准,确保与后端认证逻辑兼容 |
|
后端 |
实现健壮的认证类 |
兼容大小写混合、多余空格、无前缀等现实场景,避免因格式问题拒绝合法请求 |
|
测试 |
覆盖边界情况(如多余空格) |
确保认证类在各种异常格式下仍能正确提取令牌 |
|
日志 |
遮掩令牌敏感信息 |
防止日志泄露用户令牌,提升系统安全性 |
通过本方案,可彻底解决因Bearer前缀处理不当导致的 JWT 认证失败问题,同时兼顾标准性与健壮性,适用于生产环境。
浙公网安备 33010602011771号