aekasitt/fastapi-csrf-protect
GitHub: aekasitt/fastapi-csrf-protect
为 FastAPI 应用提供基于双重提交 Cookie 模式的无状态 CSRF 防护扩展。
Stars: 110 | Forks: 15
# FastAPI CSRF Protect
[](https://pypi.org/project/fastapi-csrf-protect)
[](https://pypi.org/project/fastapi-csrf-protect)
[](https://pypi.org/project/fastapi-csrf-protect)
[](https://pypi.org/project/fastapi-csrf-protect)
[](.)
[](.)
[](.)
[](.)
[](https://aekasitt.github.io/fastapi-csrf-protect)
[](https://github.com/aekasitt/fastapi-csrf-protect/blob/master/static/protect-banner.svg)
## 功能
提供无状态跨站请求伪造(Cross-Site Request Forgery, XSRF)保护支持的 FastAPI 扩展。
旨在实现易用和轻量化,我们采用了 [双重提交 Cookie](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#double-submit-cookie) 缓解模式。
如果您之前熟悉 `flask-wtf` 库,那么这个扩展非常适合您。
该扩展的灵感来源于 `fastapi-jwt-auth` 😀
- 将 `fastapi-csrf-token` 存储在 cookie 中,或者在模板上下文中提供
## 安装说明
开始使用此扩展最简单的方式是通过 pip
```
pip install fastapi-csrf-protect
# 或者
uv add fastapi-csrf-protect
```
## 快速入门
以下示例展示了如何将此扩展集成到 FastAPI 应用中
### 登录表单示例
```
from fastapi import FastAPI, Request, Depends
from fastapi.responses import JSONResponse
from fastapi.templating import Jinja2Templates
from fastapi_csrf_protect import CsrfProtect
from fastapi_csrf_protect.exceptions import CsrfProtectError
from pydantic_settings import BaseSettings
app = FastAPI()
templates = Jinja2Templates(directory="templates")
class CsrfSettings(BaseSettings):
secret_key: str = "asecrettoeverybody"
cookie_samesite: str = "none"
@CsrfProtect.load_config
def get_csrf_config():
return CsrfSettings()
@app.get("/login")
def form(request: Request, csrf_protect: CsrfProtect = Depends()):
"""
Returns form template.
"""
csrf_token, signed_token = csrf_protect.generate_csrf_tokens()
response = templates.TemplateResponse(
"form.html", {"request": request, "csrf_token": csrf_token}
)
csrf_protect.set_csrf_cookie(signed_token, response)
return response
@app.post("/login", response_class=JSONResponse)
async def create_post(request: Request, csrf_protect: CsrfProtect = Depends()):
"""
Creates a new Post
"""
await csrf_protect.validate_csrf(request)
response: JSONResponse = JSONResponse(status_code=200, content={"detail": "OK"})
csrf_protect.unset_csrf_cookie(response) # prevent token reuse
return response
@app.exception_handler(CsrfProtectError)
def csrf_protect_exception_handler(request: Request, exc: CsrfProtectError):
return JSONResponse(status_code=exc.status_code, content={"detail": exc.message})
```
### 如何在客户端代码中发送 CSRF token
#### HTML 表单(服务端渲染)
```
```
#### AJAX (JavaScript)
```
fetch("/items/123", {
method: "DELETE",
headers: {
"X-CSRFToken": getCookie("csrftoken")
},
credentials: "include"
});
```
### 📌 灵活模式 (fastapi_csrf_protect.flexible)
某些应用程序会在同一个项目中结合使用 **服务端渲染 (SSR)** 和 **API endpoint**。
例如:
- 使用 Jinja2 模板渲染并使用 HTML 表单的 **SSR 页面**(CSRF token 位于**表单主体**中)
- 在 **HTTP header** 中传递 CSRF token 的 **AJAX / API 调用**(例如 DELETE、PUT、PATCH)
主要的 fastapi-csrf-protect 包具有**强约定性**,并要求 CSRF token **仅存在于一个位置**(header 或 body)。
对于混合应用来说,这可能不太方便。
**灵活子包 (flexible sub-package)** 提供了 CsrfProtect 的直接替代方案,它**始终接受来自 header 或表单主体的 CSRF token**,优先级如下:
- **Header**: X-CSRFToken
- **Body**: token_key (form-data)
### 何时使用灵活模式
在以下情况下使用 fastapi_csrf_protect.flexible:
- 您在同一个项目中同时拥有 SSR 页面和 API endpoint。
- 某些请求(如 DELETE)无法发送 body,但仍需要 CSRF 验证。
- 您希望避免维护两套不同的 CSRF 配置。
如果您的应用仅使用**一种**方法发送 CSRF token,请继续使用**核心包**以获得更严格的策略。
### 前置条件
* [git](https://git-scm.com/) - --fast-version-control
* [python](https://www.python.org) 3.10 及以上版本 - 高级通用编程语言
* [uv](https://docs.astral.sh/uv) - 极速的 Python 包和项目管理器,使用 Rust 编写
以下指南将介绍如何使用 `git` 作为分布式版本控制系统,以及 `uv` 作为 Python 包和版本管理器来设置本地工作环境。
如果您尚未安装 `git`,请运行以下命令。
如果您尚未安装 `uv`,请运行以下命令。
一旦安装了 `git` 分布式版本控制系统,您就可以
克隆当前代码库,并为该项目安装 3.10 以上版本的 Python。
以下命令可帮助您设置并激活
Python 虚拟环境,随后 `uv` 便可以从 `pyproject.toml` 文件中定义的 `PyPI` 开源注册表下载项目依赖项。
### 路线图
* 修复 CI/CD 和 GitHub Pages 集成
* 在设置和运行测试时添加代码示例
* 更正 `README.md` 中的前端示例
* 添加 `flexible` 示例
* 重写 `README.md` 中的 `Flexible Mode` 部分
* 测试 `granian[uvloop]` 和 `granian[rloop]`
## 更新日志
* **0.3.1** 采用 [双重提交 Cookie](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#double-submit-cookie)
:construction: **重大变更**
0.3.0 -> 0.3.1: `generate_csrf` 已标记为弃用
* **0.3.2** 添加 `token_location` 配置(可选 `body` 或 `header`);不设置则防止 token 重用
:construction: **重大变更**
0.3.1 -> 0.3.2: `validate_csrf` 现在是异步的
* **0.3.5** 引入了与 Pydantic V2 相关的 bug,已在 0.3.6 版本中修复;影响 `cookie_samesite`
* **0.3.6** 修复了前一版本引入的 `cookie_samesite` 验证 bug
* **1.0.0** 移除已弃用的 `generate_csrf`,请使用返回元组的 `generate_csrf_tokens`
* **1.0.1** 修复了在使用 cookie `Secure` 和/或 `SameSite=None` 配置库时的 cookie 取消设置问题
* **1.0.2** 改进了 `LoadConfig` 的布尔值处理
* **1.0.3** 尝试集成 `mypyc` 编译的实验失败,原因是依赖注入模式
* **1.0.4** 添加了在省略 `token_location` 时的灵活模式以及多位置检查
:construction: **发布失败**
1.0.3 -> 1.0.4: 带着半成品代码发布;立即从 PyPI 撤回了该版本
* **1.0.5** 移除失败实验中遗留的 `@dataclass`;澄清测试下的失败原因
* **1.0.6** 修复了通过 form data 提交 token 时出现的 `Stream consumed` 问题,`isinstance` 会消耗主体
* **1.0.7** 修复了在 `Flexible Mode` 下添加预验证内容的问题;添加测试选择标志
### 运行示例
要运行提供的示例,您首先必须安装额外的依赖项
[granian](https://github.com/emmett-framework/granian) 和 [minijinja](https://github.com/mitsuhiko/minijinja/tree/main/minijinja-py)
或者,在终端运行以下命令来完成安装
```
uv sync --group=examples
```
运行使用表单提交的示例
```
granian --interface asgi examples.body:app
```
运行通过 JavaScript 使用 header 的示例
```
granian --interface asgi examples.header:app
```
## 许可证
本项目基于 MIT 许可证条款授权。
使用 Homebrew 安装 (Darwin)
``` brew install git ```通过二进制安装程序安装 (Linux)
* 基于 Debian 的包管理 ``` sudo apt install git-all ``` * 基于 Fedora 的包管理 ``` sudo dnf install git-all ```使用 Homebrew 安装 (Darwin)
``` brew install uv ```使用独立安装程序安装 (Darwin 和 Linux)
``` curl -LsSf https://astral.sh/uv/install.sh | sh ```设置环境并同步项目依赖
``` git clone git@github.com:aekasitt/fastapi-csrf-protect.git cd fastapi-csrf-protect uv venv --python 3.10.18 source .venv/bin/activate uv sync --dev ```标签:AV绕过, CSRF防护, FastAPI, Python, Syscall, Web开发, 中间件, 无后门, 网络安全, 逆向工具, 隐私保护