当前位置:

首页 > 编程开发 > FastAPI多认证方案:支持Basic与JWT验证

FastAPI多认证方案:支持Basic与JWT验证

本文详细阐述如何在FastAPI中实现灵活的多重认证机制,允许客户端通过BasicAuth或JWTBearerToken中的任意一种方式访问受保护的API端点。核心策略是将各个认证依赖项配置为在认证失败时不立即抛出异常,而是返回None,从而将最终的授权决策和错误处理集中到一个高级组合依赖中。

FastAPI实现多重可选认证:同时支持Basic Auth与JWT Auth

本文详细阐述如何在FastAPI中实现灵活的多重认证机制,允许客户端通过Basic Auth或JWT Bearer Token中的任意一种方式访问受保护的API端点。核心策略是将各个认证依赖项配置为在认证失败时不立即抛出异常,而是返回None,从而将最终的授权决策和错误处理集中到一个高级组合依赖中。

在构建现代Web API时,常常需要支持多种认证方式以适应不同的客户端或使用场景。例如,某些内部服务可能使用Basic Auth,而外部客户端则可能依赖JWT。FastAPI的依赖注入系统非常强大,但默认情况下,当一个端点声明了多个认证依赖时,FastAPI会尝试强制所有依赖都成功。这导致了一个常见问题:如何让端点支持“或”逻辑,即只要任意一种认证方式成功即可访问?

本文将通过一个具体的示例,演示如何改造FastAPI的认证依赖,使其能够灵活地支持Basic Auth或JWT Auth的任选模式。

1. 核心理念:非阻塞式认证依赖

解决此问题的关键在于,让单个的认证依赖项在认证失败时不再立即抛出HTTPException,而是返回一个指示失败的值(通常是None)。这样,上层组合依赖就可以根据这些返回值来判断最终的认证状态。

对于FastAPI内置的认证方案,如HTTPBasic和OAuth2PasswordBearer,可以通过设置auto_error=False来实现这一目标。

2. 实现非阻塞式 Basic Authentication

首先,我们需要修改HTTPBasic实例,使其在缺少或无效的Basic Auth头时不会自动抛出401错误。

from fastapi.security import HTTPBasic, HTTPBasicCredentials
from fastapi import Depends, HTTPException, status
from typing import Optional, Annotated
import secrets # 用于安全地比较字符串

# 假设 settings.SESSION_LOGIN_USER 和 settings.SESSION_LOGIN_PASS 存储了正确的用户名和密码
# 假设 router 和 db_session 已定义
# 假设 User 模型已定义

# 1. 初始化 HTTPBasic,设置 auto_error=False
security = HTTPBasic(auto_error=False)

# 2. 创建 Basic Auth 依赖函数
def basic_logged_user(credentials: Annotated[Optional[HTTPBasicCredentials], Depends(security)]):
    """
    Basic Auth 认证依赖。
    如果认证失败,不抛出异常,而是返回 None。
    """
    if credentials is None:
        # 没有提供 Basic Auth 凭据
        return None

    # 安全地比较用户名和密码
    current_username_bytes = credentials.username.encode("utf8")
    correct_username_bytes = settings.SESSION_LOGIN_USER.encode("utf8")
    is_correct_username = secrets.compare_digest(current_username_bytes, correct_username_bytes)

    current_password_bytes = credentials.password.encode("utf8")
    correct_password_bytes = settings.SESSION_LOGIN_PASS.encode("utf8")
    is_correct_password = secrets.compare_digest(current_password_bytes, correct_password_bytes)

    if not (is_correct_username and is_correct_password):
        # 凭据无效,返回 None
        return None

    # 认证成功,返回用户名
    return credentials.username

在这个basic_logged_user函数中:

  • Depends(security)会尝试解析Basic Auth凭据。由于auto_error=False,如果凭据缺失,credentials将为None。
  • 我们显式地将credentials声明为Optional[HTTPBasicCredentials],以处理None的情况。
  • 无论凭据缺失还是无效,函数都返回None,而不是抛出HTTPException。

3. 实现非阻塞式 JWT Authentication

类似地,我们需要对JWT认证方案进行调整。通常,JWT认证会使用OAuth2PasswordBearer。我们也需要将其初始化为auto_error=False。

from fastapi.security import OAuth2PasswordBearer
# ... 其他导入 ...
# 假设 utils.verify_token 是一个验证JWT并返回用户数据的函数
# 假设 db_session 和 User 模型已定义

# 1. 初始化 OAuth2PasswordBearer,设置 auto_error=False
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token", auto_error=False) # tokenUrl 根据你的实际情况设置

# 2. 创建 JWT Auth 依赖函数
def jwt_logged_user(token: Annotated[Optional[str], Depends(oauth2_scheme)], 
                    db: Session = Depends(db_session)):
    """
    JWT Auth 认证依赖。
    如果认证失败,不抛出异常,而是返回 None。
    """
    if token is None:
        # 没有提供 JWT token
        return None

    try:
        # 假设 utils.verify_token 会验证 token 并返回一个包含用户信息的对象
        # 如果 token 无效,utils.verify_token 内部应该捕获异常或返回 None
        # 这里我们假设它在验证失败时会抛出异常,我们捕获它
        payload = utils.verify_token(token) # 假设 verify_token 成功返回 payload,失败抛异常
        username = payload.get("sub") # 假设 sub 字段存储用户名
        if not username:
            return None

        user = db.query(User).filter(User.username == username).first()
        if not user:
            return None

        return user # 认证成功,返回用户对象
    except Exception:
        # token 验证失败(过期、无效签名等)
        return None

在这个jwt_logged_user函数中:

  • Depends(oauth2_scheme)会尝试从请求头中提取JWT token。由于auto_error=False,如果token缺失,token参数将为None。
  • 我们同样将token声明为Optional[str]。
  • utils.verify_token应该被设计为在验证失败时抛出异常或返回一个可识别的失败状态。这里我们用try-except块来捕获验证失败的情况,并返回None。

4. 创建组合认证依赖 (auth_user)

现在我们有了两个非阻塞式的认证依赖,可以创建一个新的依赖函数来组合它们,实现“或”逻辑。

from fastapi import HTTPException, status
# ... 其他导入 ...

def auth_user(jwt_auth_result: Annotated[Optional[User], Depends(jwt_logged_user)], 
              basic_auth_result: Annotated[Optional[str], Depends(basic_logged_user)]):
    """
    组合认证依赖,支持 Basic Auth 或 JWT Auth。
    如果任一认证方式成功,则返回相应的认证用户。
    如果两种方式都失败,则抛出 401 异常。
    """
    if jwt_auth_result:
        # JWT 认证成功
        return jwt_auth_result

    if basic_auth_result:
        # Basic Auth 认证成功
        # 注意:这里返回的类型需要与 jwt_auth_result 的类型保持一致,
        # 或者根据你的业务逻辑决定返回哪个。
        # 为了简化,这里假设可以返回一个字符串作为认证成功的标识。
        # 如果需要返回统一的用户对象,则需要从 basic_auth_result 中获取用户对象。
        return basic_auth_result

    # 两种认证方式都失败,抛出 401 未授权异常
    raise HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED, 
        detail='Invalid Credentials',
        headers={"WWW-Authenticate": "Bearer, Basic"} # 提示客户端支持的认证方式
    )

在这个auth_user函数中:

  • 它接收jwt_logged_user和basic_logged_user的返回值。
  • 如果jwt_auth_result不为None(即JWT认证成功),则直接返回该结果。
  • 否则,如果basic_auth_result不为None(即Basic Auth认证成功),则返回该结果。
  • 只有当两种认证方式都返回None时,才抛出HTTP_401_UNAUTHORIZED异常。

5. 在API端点中使用组合认证

最后,将auth_user依赖添加到你的API端点中。

from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
# ... 其他导入 ...

router = APIRouter() # 假设你有一个 APIRouter 实例

@router.get("/users")
async def get_users(db: Session = Depends(db_session), 
                    logged_user: Annotated[Union[User, str], Depends(auth_user)]):
    """
    获取用户列表,需要 Basic Auth 或 JWT Auth 认证。
    """
    query_users = db.query(User).all()
    return query_users

现在,当客户端请求/users端点时:

  • 如果提供了有效的JWT Bearer Token,即使没有提供Basic Auth,请求也会成功。
  • 如果提供了有效的Basic Auth凭据,即使没有提供JWT Token,请求也会成功。
  • 只有当两种认证方式的凭据都缺失或无效时,才会收到401 Unauthorized响应。

总结与注意事项

通过将各个认证依赖项配置为非阻塞模式(auto_error=False并返回None),并创建一个中央组合依赖来处理最终的授权逻辑,我们成功地实现了FastAPI中多重可选认证的需求。

注意事项:

  1. 返回类型统一: 在auth_user中,jwt_auth_result可能返回User对象,而basic_auth_result可能返回str(用户名)。在实际应用中,你可能希望将它们统一为某种形式的用户对象,例如通过用户名从数据库中加载用户对象,确保auth_user始终返回User类型或其接口类型。
  2. 错误信息: 在auth_user抛出HTTPException时,WWW-Authenticate头部应包含所有支持的认证方案,以便客户端知道如何进行认证。
  3. 安全性: 在实现认证逻辑时,务必使用secrets.compare_digest等安全函数来比较密码,以防止定时攻击。
  4. 可扩展性: 这种模式非常灵活,你可以轻松地添加更多可选的认证方案(如API Key认证),只需为每个方案创建非阻塞依赖,并在auth_user中进行组合。

通过遵循这些原则,你可以构建出既安全又灵活的FastAPI认证系统。

本文内容来源于互联网,如有侵权请联系删除。
作者最新文章
编程开发
相关文章 更多
C++动态数组初始化怎么写?常用语句与代码示例
C++动态数组初始化怎么写?常用语句与代码示例

深入解析C++中动态数组的初始化机制,涵盖new操作符的不同用法、基本类型与类对象的初始化差异,以及为何在现代C++开发中应优先使用std::vector。

using namespace 使用中遇到的问题怎么解决
using namespace 使用中遇到的问题怎么解决

命名空间的基本概念与常见引入问题在C++等编程语言中,命名空间(namespace)是一种将代码标识符(如变量、函数、类名)封装在特定名称下的机制,其主要目的是避免命名冲突,尤其是在大型项目或使用多个第三方库时。使用“using namespace”指令可以将指定命名空间中的所有名称引入当前作用域,

c语言函数递归 实操经验总结:这些技巧很实用
c语言函数递归 实操经验总结:这些技巧很实用

理解递归的基本原理在C语言中,递归是一种函数调用自身的编程技术。要掌握它,首先需要理解其核心思想:将一个复杂的大问题,分解为一个或几个与原问题相似但规模更小的子问题,直到子问题足够简单,可以直接求解。这个过程通常包含两个关键部分:递归出口和递归体。递归出口定义了问题何时不再继续分解,即最简单、可直接

c语言函数递归 怎么选?常见方案对比分析
c语言函数递归 怎么选?常见方案对比分析

递归函数的基本概念与适用场景在C语言编程中,递归是一种函数调用自身的编程技巧。它并非适用于所有问题,但在处理某些具有自相似结构的问题时,能提供极其清晰和优雅的解决方案。递归的核心思想是将一个大规模问题分解为一个或多个同类型但规模更小的子问题,直到子问题简单到可以直接求解。典型的适用场景包括树形结构的

Objective-C 内存管理入门:从 alloc 到 dealloc 的生命周期详解
Objective-C 内存管理入门:从 alloc 到 dealloc 的生命周期详解

理解内存管理的基石在Objective-C的编程世界中,内存管理是开发者必须掌握的核心技能之一。它直接关系到应用的性能、稳定性与资源利用效率。与一些采用自动垃圾回收机制的语言不同,Objective-C在很长一段时间里,依赖一套基于引用计数的、需要开发者部分介入的管理规则。这套规则的核心思想是明确的

如何正确使用 dealloc 以避免 iOS 应用中的内存泄漏
如何正确使用 dealloc 以避免 iOS 应用中的内存泄漏

理解 dealloc 的角色与时机在 iOS 应用开发中,内存管理是保障应用性能与稳定性的基石。dealloc 方法是 Objective-C 中对象生命周期结束时的关键回调,它标志着对象即将被系统回收内存。正确理解其触发时机至关重要:当一个对象的引用计数降为零时,运行时系统会自动调用该对象的 de

深入理解 Objective-C 中的 dealloc 方法:内存管理核心机制
深入理解 Objective-C 中的 dealloc 方法:内存管理核心机制

内存管理的基石在Objective-C的世界里,内存管理是开发者必须掌握的核心技能之一。作为一门在手动引用计数(MRC)时代诞生的语言,Objective-C要求程序员对对象的生命周期有清晰的认识。dealloc方法正是这一生命周期中至关重要的终点站。它是一个实例方法,当对象的引用计数降为零时,系统

理解 native2ascii:Java 国际化开发中的字符编码工具
理解 native2ascii:Java 国际化开发中的字符编码工具

native2ascii 工具的基本定位在Ja va应用程序的国际化与本地化开发过程中,处理非拉丁字符集是一个常见且关键的环节。Ja va内部使用Unicode字符集来统一表示全球各种语言的文字,但其属性文件(.properties)在历史上要求使用ASCII编码,或者更准确地说,要求非ASCII字

如何使用 native2ascii 转换中文字符为 Unicode 转义序列
如何使用 native2ascii 转换中文字符为 Unicode 转义序列

理解 native2ascii 工具的基本用途在软件开发,特别是涉及国际化处理的场景中,开发者常常需要处理不同编码的文本资源。native2ascii 是 Ja va 开发工具包(JDK)中提供的一个命令行实用程序,其主要功能是将包含本地字符编码(非ASCII字符)的文件,转换为包含 Unicode

Java native2ascii 命令详解:解决属性文件乱码问题
Java native2ascii 命令详解:解决属性文件乱码问题

native2ascii 命令的由来与作用在Ja va开发中,处理国际化资源文件是一个常见需求。资源文件通常以.properties格式存储,用于支持多语言界面。然而,Ja va属性文件默认采用ISO-8859-1字符集编码,这导致了一个直接的问题:当文件中包含非拉丁字符(如中文、日文、韩文等)时,

查看更多
精品专题 更多
装机必备
装机必备

正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

Windows
Windows

正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

macOS软件
macOS软件

正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

Mac软件 更多
灵活计算器
灵活计算器
macOS/iOS/Android

灵活计算器是一款笔记式算数应用,支持实时计算、动态关联和云端同步功能。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

赤友清理大师
赤友清理大师
macOS

赤友清理大师是一款为 Mac 设计的智能清理优化工具,可精准扫描垃圾、大文件、重复文件等,释放磁盘空间。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

极度公式
极度公式
Windows/macOS/Linux

极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

WINDOWS 更多
Windows 10
Windows 10
Windows

Windows 10 是一款微软推出的经典操作系统,拥有硬件兼容性与多任务处理能力。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。

极度公式
极度公式
Windows/macOS/Linux

极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

密码键盘
密码键盘
Windows/macOS/iOS/Android

密码键盘是一款兼具安全性与便捷性的高效密码管理器。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。