vyacheres/Social_media_project
GitHub: vyacheres/Social_media_project
一个基于 FastAPI 的教学型社交网络 Web 应用,展示了用户认证、CSRF 防护、频率限制、安全响应头等 Web 安全最佳实践,并附带完整的 pytest 集成测试。
Stars: 0 | Forks: 0
# 社交网络 (Social Media Project)
基于 **FastAPI** 的教学 Web 应用:帖子信息流、评论、搜索、名言演示页面、JSON API。实现了 **注册与登录**、表单 **CSRF** 防护、请求频率限制 (**slowapi**)、安全的 HTTP 响应头以及 API 访问控制。
## 目录 (RU)
1. [功能](#возможности)
2. [技术栈](#стек-технологий)
3. [仓库结构](#структура-репозитория)
4. [快速开始](#быстрый-старт)
5. [环境变量](#переменные-окружения)
6. [路由](#маршруты)
7. [安全性](#безопасность)
8. [数据库](#база-данных)
9. [测试](#тестирование)
10. [许可证](#лицензия)
11. [英文文档](#social-media-project-english-documentation)
## 功能
| 领域 | 描述 |
|--------|----------|
| 信息流 | 显示帖子列表的主页(CRUD 代码中每次请求最多 100 条记录)。 |
| 帖子 | 查看帖子详情、评论;**浏览量计数器** 在**同一浏览器会话中每个帖子最多增加一次**。 |
| 作者 | 帖子和评论与 **登录用户的用户名** 绑定;无法通过 HTML 表单伪造其他作者姓名。 |
| 搜索 | 在标题和正文中的子字符串匹配;`%` 和 `_` **不** 作为 SQL `LIKE` 通配符。 |
| 个人主页 | `/users/{username}` 页面列出数据库中 `author` 字段与该用户名匹配的所有帖子。 |
| 名言 | 通过 [Quotable API](https://api.quotable.io) 获取随机名言的页面。 |
| API | JSON 列表和详情接口 —— 仅限 **登录后的会话**,或者在配置了 `API_KEY` 时使用 **`X-API-Key`** 请求头访问。 |
## 技术栈
| 组件 | 作用 |
|-----------|------|
| **FastAPI** | HTTP API 和通过 Jinja2 渲染的 HTML 响应。 |
| **SQLAlchemy 2** | ORM,包含 `User`、`Post`、`Comment` 模型。 |
| **SQLite** | 默认数据库文件为 `social_media.db`(可通过 `DATABASE_URL` 配置路径)。 |
| **Jinja2** | 位于 `Templates/` 目录下的模板。 |
| **Starlette** | 会话、中间件、测试中的 `TestClient`。 |
| **bcrypt** | 密码哈希。 |
| **slowapi** | 对敏感的 POST 路由进行频率限制。 |
| **httpx** | 用于请求外部名言 API 的异步客户端。 |
| **pytest** | `tests/` 目录下的集成测试。 |
## 仓库结构
```
Social_media_project/
├── main.py # Точка входа FastAPI: маршруты, middleware, лимиты
├── database.py # Engine, SessionLocal, get_db, declarative Base
├── models.py # ORM: User, Post, Comment
├── crud.py # Операции с БД + безопасный поиск LIKE
├── init_db.py # Создание таблиц и демо-данные при пустой ленте
├── settings.py # SECRET_KEY, DATABASE_URL, API_KEY, флаги из .env
├── schemas.py # Pydantic-схемы для постов, комментариев, регистрации
├── auth_utils.py # bcrypt и CSRF-токен в сессии
├── middlewares.py # Заголовки безопасности и подготовка CSRF
├── requirements.txt
├── .env.example # Пример переменных окружения (без секретов)
├── test_functionality.py # Ручные проверки CRUD в консоли
├── simple_test.py # Проверка файла social_media.db без сервера
├── tests/
│ ├── conftest.py # Фикстура клиента и in-memory SQLite (StaticPool)
│ └── test_app.py # Интеграционные сценарии (см. таблицу ниже)
├── Static/
│ └── style.css
└── Templates/
├── index.html, post.html, search.html, user.html, quote.html
├── login.html, register.html, create_post.html
└── partials/ # nav.html, csrf_field.html
```
## 快速开始
**前提条件:** Python 3.10+(推荐 3.11+),`pip`。
```
cd Social_media_project
pip install -r requirements.txt
copy .env.example .env # Windows; на Linux/macOS: cp .env.example .env
# 编辑 .env:在任何公开部署前设置一个强 SECRET_KEY。
python init_db.py
uvicorn main:app --reload --host 127.0.0.1 --port 8000
```
在浏览器中打开:**http://127.0.0.1:8000** —— 注册一个用户,然后就可以创建帖子和评论了。
使用 uvicorn 运行的替代方法:
```
python main.py
```
## 环境变量
| 变量 | 用途 |
|------------|------------|
| `SECRET_KEY` | 用于签名 cookie 会话(**务必**在生产环境中更改,长度 ≥ 32 个字符)。 |
| `DATABASE_URL` | SQLAlchemy 连接字符串(默认为 `sqlite:///./social_media.db`)。 |
| `API_KEY` | 如果设置此变量,则可以通过 `X-API-Key: <值>` 请求头访问 JSON API,而无需在浏览器中登录。 |
| `DISABLE_RATE_LIMIT` | 值为 `1` / `true` / `yes` 时,将禁用 slowapi(方便用于 CI 和 pytest)。 |
详情请参阅 **`.env.example`** 文件。
## 路由
### HTML
| 方法 | URL | 用途 |
|-------|-----|------------|
| GET | `/` | 主页,帖子列表 |
| GET | `/posts/create` | 新建帖子表单(需要会话) |
| POST | `/posts/create` | 创建帖子 |
| GET | `/posts/{id}` | 帖子详情与评论 |
| POST | `/posts/{id}/comment` | 发表新评论 |
| GET | `/search?s=...` | 搜索 |
| GET | `/users/{username}` | 该作者的帖子 |
| GET/POST | `/register`、`/login`、POST `/logout` | 账号管理 |
| GET | `/random_quote` | 随机名言 |
### JSON API
| 方法 | URL | 访问权限 |
|-------|-----|--------|
| GET | `/api/posts` | 登录会话 **或** 有效的 `X-API-Key`(如果配置了 `API_KEY`) |
| GET | `/api/posts/{id}` | 同上 |
使用密钥的示例:
```
curl -H "X-API-Key: ваш_секрет" http://127.0.0.1:8000/api/posts
```
## 安全性
- 密码仅以 **bcrypt 哈希** 的形式存储;不保存明文。
- **CSRF:** 在所有修改状态的 POST 表单中包含隐藏字段,并在服务器端进行验证。
- **会话:** 签名的 cookie(`SessionMiddleware`,`SECRET_KEY`)。
- **响应头:** `X-Content-Type-Options`、`X-Frame-Options`、`Referrer-Policy`。
- **登录后重定向:** 仅允许形如 `/...` 的相对路径,拒绝 `//`(防止开放重定向攻击)。
- 在注册、登录、创建帖子和创建评论时启用 **Rate limiting(频率限制)**。
## 数据库
运行 `python init_db.py` 后,将创建 SQLite 文件(文件名取决于 `DATABASE_URL`)。
主要数据表:
- **`users`** — `id`、`username`、`password_hash`
- **`posts`** — `id`、`title`、`content`、`author`、`views`、`likes`
- **`comments`** — `id`、`post_id`、`author`、`content`
`init_db.py` 中的演示帖子使用了诸如 “Иван”、“Мария” 等作者名;注册后,新帖子的 `author` 字段将使用 **您的用户名**。
## 测试
### Pytest(主要测试套件)
从仓库根目录运行:
```
pytest tests/ -v
```
`tests/conftest.py` 中的 **`client`** 夹具 会将 `get_db` 替换为一个使用 **`StaticPool`** 的内存 SQLite 数据库(在测试中的所有连接共享同一个数据库),创建表结构,并在导入应用之前设置 `SECRET_KEY` 和 `DISABLE_RATE_LIMIT` 环境变量。
以下是 **`tests/test_app.py`** 中的所有自动化测试(共 12 个)。
| 测试 | 验证内容 |
|------|----------------|
| `test_api_posts_unauthorized` | 没有会话且没有 `X-API-Key` 的 `GET /api/posts` 请求返回 **401**。 |
| `test_api_posts_with_session` | 成功注册(303)后,同一个客户端请求 `GET /api/posts` 得到 **200** 以及空的 JSON 数组 `[]`。 |
| `test_api_posts_with_api_key` | 在对 `settings.api_key` 进行模拟替换后,提供正确的 **`X-API-Key`** 请求头会返回 **200**,提供错误的密钥则返回 **401**。 |
| `test_search_percent_literal` | 搜索 `100%` 和 `%` 时,`%` 被视为普通字符(不会作为通配符扩展);标题中包含 `%` 的帖子会被找到,不包含 `%` 的帖子不会出现在搜索 `%` 的结果中。 |
| `test_comment_csrf_required` | 已登录用户在 POST 评论时提供 **错误的** `csrf_token` 会得到 **403**。 |
| `test_comment_requires_login` | 在没有会话的情况下,使用 `/register` 获取的有效 CSRF 发表 POST 评论会导致 **303**,并且 `Location` 包含 **`/login`**。 |
| `test_view_increment_once_per_session` | 连续两次对同一个帖子执行 `GET` 请求,**`views` 只会增加到 1**(会话内去重)。 |
| `test_create_post_redirect_without_login` | 未登录状态下使用有效的 CSRF 执行 `POST /posts/create` 会返回 **303** 并重定向到登录页面。 |
| `test_register_password_mismatch` | 使用不匹配的 `password` 和 `password2` 进行注册会返回 **400**。 |
| `test_security_headers` | `GET /` 请求返回 **`X-Frame-Options: DENY`** 和 **`X-Content-Type-Options: nosniff`**。 |
| `test_logged_in_comment_flow` | 端到端流程:注册 -> 创建帖子(作者来自会话) -> 评论;响应的 HTML 中可以看到评论文本。 |
| `test_login_open_redirect_blocked` | 登录后,指向绝对 URL(`https://...`)的 `next` 参数会被忽略 —— 重定向到 **`/`**,而不是外部站点。 |
### 其他脚本(非 pytest)
| 文件 | 用途 |
|------|------------|
| `test_functionality.py` | 控制台脚本:检查数据库表、CRUD 操作,输出到 stdout(手动运行)。 |
| `simple_test.py` | 通过 `sqlite3` 验证 `social_media.db` 文件是否存在以及 `posts`、`comments`、`users` 表是否存在。 |
## 许可证
MIT License
# Social Media Project — 英文文档
基于 **FastAPI** 的教学 Web 应用:帖子信息流、评论、搜索、名言演示页面以及 JSON API。包含 **注册与登录**、表单 **CSRF** 防护、**slowapi** 频率限制、安全相关的 HTTP 响应头以及受限的 API 访问。
## 目录 (EN)
1. [功能](#features)
2. [技术栈](#tech-stack)
3. [仓库结构](#repository-layout)
4. [快速开始](#quick-start)
5. [环境变量](#environment-variables)
6. [路由](#routes)
7. [安全性](#security)
8. [数据库](#database)
9. [测试](#testing)
10. [许可证](#license)
## 功能
| 领域 | 描述 |
|------|---------------|
| 信息流 | 显示帖子列表的主页(默认 CRUD 调用每次最多获取 100 条)。 |
| 帖子 | 带有评论的帖子页面;**浏览量计数器** 在**同一浏览器会话中每个帖子最多增加一次**。 |
| 作者 | 帖子和评论与 **登录用户的用户名** 绑定;HTML 表单无法伪造其他作者。 |
| 搜索 | 在标题和正文中进行子字符串匹配;`%` 和 `_` **不** 被视为 SQL `LIKE` 通配符。 |
| 个人主页 | `/users/{username}` 列出 `author` 字段与该字符串匹配的帖子。 |
| 名言 | 使用 [Quotable API](https://api.quotable.io) 的随机名言页面。 |
| API | JSON 列表/详情接口 —— 仅限 **登录后的会话**,或者在配置了 `API_KEY` 时使用 **`X-API-Key`** 访问。 |
## 技术栈
| 组件 | 作用 |
|-------|------|
| **FastAPI** | 通过 Jinja2 提供 HTTP API 和 HTML。 |
| **SQLAlchemy 2** | ORM 模型 `User`、`Post`、`Comment`。 |
| **SQLite** | 默认数据库文件 `social_media.db`(可通过 `DATABASE_URL` 覆盖)。 |
| **Jinja2** | `Templates/` 目录下的模板。 |
| **Starlette** | 会话、中间件、测试中的 `TestClient`。 |
| **bcrypt** | 密码哈希。 |
| **slowapi** | 对敏感的 POST 路由进行频率限制。 |
| **httpx** | 用于请求外部名言 API 的异步客户端。 |
| **pytest** | `tests/` 目录下的集成测试。 |
## 仓库结构
```
Social_media_project/
├── main.py # FastAPI entry: routes, middleware, limits
├── database.py # Engine, SessionLocal, get_db, declarative Base
├── models.py # ORM: User, Post, Comment
├── crud.py # DB helpers + safe LIKE search
├── init_db.py # create_all + demo seed when posts table is empty
├── settings.py # SECRET_KEY, DATABASE_URL, API_KEY, flags from .env
├── schemas.py # Pydantic schemas for posts, comments, registration
├── auth_utils.py # bcrypt + CSRF token in session
├── middlewares.py # Security headers + CSRF priming middleware
├── requirements.txt
├── .env.example
├── test_functionality.py # Manual console checks for CRUD
├── simple_test.py # Checks social_media.db file and tables
├── tests/
│ ├── conftest.py # TestClient + in-memory SQLite (StaticPool)
│ └── test_app.py # Integration scenarios (see table below)
├── Static/
│ └── style.css
└── Templates/
├── index.html, post.html, search.html, user.html, quote.html
├── login.html, register.html, create_post.html
└── partials/ # nav.html, csrf_field.html
```
## 快速开始
**前提条件:** Python 3.10+(推荐 3.11+),`pip`。
```
cd Social_media_project
pip install -r requirements.txt
cp .env.example .env # Windows: copy .env.example .env
# Edit .env: set a strong SECRET_KEY before any public deployment.
python init_db.py
uvicorn main:app --reload --host 127.0.0.1 --port 8000
```
打开 **http://127.0.0.1:8000**,注册,然后创建帖子和评论。
替代方案:
```
python main.py
```
## 环境变量
| 变量 | 用途 |
|----------|---------|
| `SECRET_KEY` | 用于签名会话 cookie(**在生产环境中务必更改**,长度 ≥ 32)。 |
| `DATABASE_URL` | SQLAlchemy URL(默认为 `sqlite:///./social_media.db)。 |
| `API_KEY` | 设置后,JSON API 接受 `X-API-Key: ` 而无需浏览器会话。 |
| `DISABLE_RATE_LIMIT` | `1` / `true` / `yes` 会禁用 slowapi(方便用于 CI/pytest)。 |
详情请参阅 **`.env.example`**。
## 路由
### HTML
| 方法 | URL | 用途 |
|--------|-----|---------|
| GET | `/` | 主页,帖子列表 |
| GET | `/posts/create` | 新建帖子表单(需要会话) |
| POST | `/posts/create` | 创建帖子 |
| GET | `/posts/{id}` | 帖子 + 评论 |
| POST | `/posts/{id}/comment` | 发表新评论 |
| GET | `/search?s=...` | 搜索 |
| GET | `/users/{username}` | 根据作者字符串列出帖子 |
| GET/POST | `/register`、`/login`、POST `/logout` | 账号管理 |
| GET | `/random_quote` | 随机名言 |
### JSON API
| 方法 | URL | 访问权限 |
|--------|-----|--------|
| GET | `/api/posts` | 登录会话 **或** 有效的 `X-API-Key`(如果配置了 `API_KEY`) |
| GET | `/api/posts/{id}` | 同上 |
示例:
```
curl -H "X-API-Key: your_secret" http://127.0.0.1:8000/api/posts
```
## 安全性
- 密码仅存储为 **bcrypt 哈希**。
- **CSRF** 在修改状态的表单上包含隐藏字段,并在服务器端验证。
- **会话:** 签名的 cookie(`SessionMiddleware`,`SECRET_KEY`)。
- **响应头:** `X-Content-Type-Options`、`X-Frame-Options`、`Referrer-Policy`。
- **登录后重定向:** 仅允许形如 `/...` 的相对路径,拒绝 `//...`(防止开放重定向攻击)。
- 在注册、登录、创建帖子、创建评论时应用 **Rate limits(频率限制)**。
## 数据库
运行 `python init_db.py` 后,将创建 SQLite 文件(文件名取决于 `DATABASE_URL`)。
主要数据表:
- **`users`** — `id`、`username`、`password_hash`
- **`posts`** — `id`、`title`、`content`、`author`、`views`、`likes`
- **`comments`** — `id`、`post_id`、`author`、`content`
演示帖子使用诸如 “Иван” 的显示名称;注册后的新帖子在 `author` 中使用 **您的用户名**。
## 测试
### Pytest(主要测试套件)
```
pytest tests/ -v
```
`tests/conftest.py` 中的 **`client`** 夹具 会将 `get_db` 替换为一个使用 **`StaticPool`** 的内存 SQLite 数据库(跨连接共享数据库),创建表结构,并在导入应用之前设置 `SECRET_KEY` 和 `DISABLE_RATE_LIMIT`。
所有自动化测试位于 **`tests/test_app.py`**(12 个测试)。
| 测试 | 验证内容 |
|------|----------------|
| `test_api_posts_unauthorized` | 没有会话且没有 `X-API-Key` 的 `GET /api/posts` 请求返回 **401**。 |
| `test_api_posts_with_session` | 成功注册(303)后,同一个客户端请求 `GET /api/posts` 得到 **200** 和 JSON `[]`。 |
| `test_api_posts_with_api_key` | 在对 `settings.api_key` 进行 monkeypatch 后,正确的 **`X-API-Key`** 会产生 **200**,错误的密钥产生 **401**。 |
| `test_search_percent_literal` | 查询 `100%` 和 `%` 时将 `%` 视为普通字符(无通配符扩展);不含 `%` 的帖子会按预期从 `%` 结果中排除。 |
| `test_comment_csrf_required` | 已登录用户使用 **错误的** `csrf_token` 发表评论会得到 **403**。 |
| `test_comment_requires_login` | 在没有会话的情况下,使用 `/register` 获取的 CSRF 发表 POST 评论返回 **303**,并且 `Location` 包含 **`/login`**。 |
| `test_view_increment_once_per_session` | 对同一帖子的两次 `GET` 请求使 **`views` 保持为 1**(每次会话去重)。 |
| `test_create_post_redirect_without_login` | 未登录时使用有效 CSRF 的 `POST /posts/create` 会返回 **303** 跳转到登录页。 |
| `test_register_password_mismatch` | 使用不匹配的密码注册返回 **400**。 |
| `test_security_headers` | `GET /` 请求返回 **`X-Frame-Options: DENY`** 和 **`X-Content-Type-Options: nosniff`**。 |
| `test_logged_in_comment_flow` | 端到端:注册 -> 创建帖子(作者来自会话) -> 评论;评论文本出现在 HTML 中。 |
| `test_login_open_redirect_blocked` | 登录后,指向 `https://...` 的 `next` 参数被忽略 —— 重定向到 **`/`**,而不是外部站点。 |
### 其他脚本(非 pytest)
| 文件 | 用途 |
|------|---------|
| `test_functionality.py` | 面向控制台的表和 CRUD 检查。 |
| `simple_test.py` | 通过 `sqlite3` 验证 `social_media.db` 存在以及所需的表(`posts`、`comments`、`users`)。 |
## 许可证
MIT License
标签:API密钥认证, AV绕过, CISA项目, CRUD操作, CSRF防护, DNS解析, FastAPI, HTTP安全头, Jinja2, JSON API, ORM, Pytest, Python, RESTful API, Slowapi, SQLAlchemy, SQLite, Streamlit, UI渲染, Web安全, Web应用开发, Web开发框架, 会话管理, 信息流, 功能测试, 单元测试, 学习资源, 安全教育, 安全规则引擎, 开源项目, 慢API限制, 提示词优化, 搜索引擎, 无后门, 模板引擎, 用户认证, 登录注册, 社交网络, 网络安全, 蓝队分析, 访问控制, 评论系统, 运行时操纵, 逆向工具, 隐私保护, 集成测试