eagleye

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 认证失败问题,同时兼顾标准性与健壮性,适用于生产环境。

 

posted on 2025-07-02 11:50  GoGrid  阅读(346)  评论(0)    收藏  举报

导航