feat(auth): 增加登录验证和五天会话

This commit is contained in:
admin committed 2026-09-21 17:46:43 +08:00
1 parent ecd16bcefc
commit 810b52a051
14 files changed
+467 -20

No files matched your search

+11 -1
View File
@@ -45,10 +45,20 @@
GitHub Actions 的 `.github/workflows/deploy.yml` 只监听 `master` 分支。分支 push 后,Actions 使用仓库 Secrets 中的服务器地址、用户和 SSH 私钥登录服务器,进入 `/opt/weread-board/WeReadBoard` 后调用 `deploy.sh`。手动触发也固定部署 `master`,部署脚本会拒绝其他分支。
`deploy.sh` 固定拉取 `master`,收到其他部署分支参数时直接退出,再检查项目 `.venv`。如果 `.venv/bin/python` 不存在,脚本使用服务器上的 `python3` 或 `python` 创建虚拟环境;随后用 `.venv/bin/python -m pip install -r requirements.txt` 安装依赖,执行 `migrations/001_reading_goals.sql`,最后重启 `weread.service`。
`deploy.sh` 固定拉取 `master`,收到其他部署分支参数时直接退出,再检查项目 `.venv`。如果 `.venv/bin/python` 不存在,脚本使用服务器上的 `python3` 或 `python` 创建虚拟环境;随后用 `.venv/bin/python -m pip install -r requirements.txt` 安装依赖,按文件名顺序执行 `migrations/*.sql`,最后重启 `weread.service`。
例:用户将已验收代码合并并推送到 `master`,服务器原来没有 `.venv`。部署脚本会在 `/opt/weread-board/WeReadBoard/.venv` 创建环境,安装 Flask 和 PyMySQL,确认阅读目标表存在,再让 systemd 重新用 `.venv/bin/python app.py` 启动服务。仅推送 `dev` 不会触发部署。
## 正式看板登录认证
`auth.py` 注册全站请求拦截和登录路由。`/` 与所有 `/api/*` 数据接口都需要 `session["auth_user"]`;静态资源、`/login`、`/auth/login` 和首次初始化路由是唯一公开入口。页面请求未认证时重定向到登录页,API 返回 401,避免前端遮罩之外仍可直接读取数据。旧 `/api/v2/*` 路由同样由 Flask 全局拦截。
账号表 `weread_auth` 固定使用 `id=1` 保存单管理员的用户名和 Werkzeug 密码哈希。数据库无账号时,只有提交服务端 `WEREAD_AUTH_SETUP_TOKEN` 才能插入首个账号;例如提交用户名 `reader`、一次性口令正确、密码 `a-long-private-password`,数据库保存的是哈希而不是密码。插入成功后创建永久 Flask 会话,并清空当前进程中的初始化口令。后续登录从 MySQL 取哈希校验;用户名或密码错误统一返回“账号或密码不正确”。
会话签名密钥从 `WEREAD_SESSION_SECRET` 读取且必须稳定配置;缺失时服务对动态页面和 API 以 503 失败关闭。永久会话时长是固定 5 天,不因每次请求续期。Cookie 为 HttpOnly、SameSite=Lax,并默认 Secure;仅本机 HTTP 预览可以关闭 Secure。生产站点须使用 HTTPS。前端 JSON 写请求从页面 meta 标签读取 CSRF token 并放入 `X-CSRF-Token` 请求头,表单请求使用隐藏字段。
应用未内置登录尝试限流;公网部署应在可信反向代理上为 `POST /auth/login` 设置频率限制,且不要直接信任未校验的 `X-Forwarded-For`。
## 示例与真实接口的边界
概览图表使用固定样例;目标日期状态使用浏览器当前本地日期。笔记导出包含当前选中书目的划线和个人想法全集,不受内容类型筛选影响,不包含书签内容及公开点评。
+9 -2
View File
@@ -1,6 +1,6 @@
# master 自动部署
GitHub Actions workflow 位于 `.github/workflows/deploy.yml`。每次 `master` 分支 push 后,Actions 会通过 SSH 登录服务器,在部署目录执行根目录 `deploy.sh`,拉取最新 `master`、安装依赖、执行阅读目标表迁移并重启服务。手动触发时也固定部署 `master`,不能选择其他分支发布。
GitHub Actions workflow 位于 `.github/workflows/deploy.yml`。每次 `master` 分支 push 后,Actions 会通过 SSH 登录服务器,在部署目录执行根目录 `deploy.sh`,拉取最新 `master`、安装依赖、执行全部数据库迁移并重启服务。手动触发时也固定部署 `master`,不能选择其他分支发布。
## GitHub 配置
@@ -32,7 +32,7 @@ git checkout master
git pull --ff-only origin master
python3 -m venv .venv # 仅 .venv 不存在时执行
.venv/bin/python -m pip install -r requirements.txt
# 读取 reading_api.MYSQL_CONFIG 后执行 migrations/001_reading_goals.sql
# 读取 reading_api.MYSQL_CONFIG 后按顺序执行 migrations/*.sql
sudo systemctl restart "$SERVICE_NAME"
```
@@ -56,6 +56,9 @@ Environment=WEREAD_DB_USER=weread
Environment=WEREAD_DB_PASSWORD=change-me
Environment=WEREAD_DB_NAME=weread
Environment=PORT=8770
Environment=WEREAD_SESSION_SECRET=<服务器生成的随机密钥>
Environment=WEREAD_AUTH_SETUP_TOKEN=<仅首次初始化使用的随机口令>
Environment=WEREAD_SESSION_COOKIE_SECURE=1
ExecStart=/opt/weread-board/WeReadBoard/.venv/bin/python app.py
Restart=always
RestartSec=3
@@ -65,3 +68,7 @@ WantedBy=multi-user.target
```
如果服务名或部署目录不同,在仓库 Variables 中调整 `SERVICE_NAME` 和 `DEPLOY_PATH`。
登录认证还需配置 `WEREAD_SESSION_SECRET` 和首次使用的 `WEREAD_AUTH_SETUP_TOKEN`。在服务器分别运行两次 `python3 -c 'import secrets; print(secrets.token_urlsafe(48))'` 生成不同的值。迁移自动创建 `weread_auth` 表;首次访问 `/login` 时输入初始化口令、管理员账号和至少 12 位密码。初始化成功后,从 systemd 配置中删除 `WEREAD_AUTH_SETUP_TOKEN` 并重启服务。数据库中的密码只保存哈希。
`WEREAD_SESSION_COOKIE_SECURE=1` 要求浏览器通过 HTTPS 访问。仅本机 HTTP 验收可临时设为 `0`,不要在公网 HTTP 下传输登录密码。
+2 -1
View File
@@ -55,6 +55,7 @@
| F16 | 数据状态:加载、空、失败及重试;演示来源常驻 | 已验证(示例) | 待实现 | `web-demo/app.js:158` `renderDiscover` |
| F17 | API Key 仅服务端使用;版本上报、升级中断、错误转换、分页 | 不属于本轮 Demo | 待实现 | 后续核对 `app.py` 和 `fetch_data.py`,复用现有服务端能力 |
| F18 | 可选统计:阅读时段、作者/出版社/版权方、阅读听书比例、好友排行、勋章、年报 | 已验证(示例) | 待实现;按实际返回字段显示 | `web-demo/app.js:85` `renderOverview` |
| F19 | 单管理员登录保护所有页面/API;首次初始化需一次性口令;密码哈希存 MySQL;会话固定 5 天 | 不适用 | 已实现,真实 MySQL 初始化待部署验证 | `auth.py`、`migrations/002_site_auth.sql`、`templates/login.html` |
## 全部文档接口的落点
@@ -122,6 +123,6 @@
| ID | 功能与验收标准 | Demo 状态 | 真实接入状态 | 代码位置 |
|---|---|---|---|---|
| F19 | 待填写;涉及新规则时追加 R 编号 | 待实现 | 待实现 | 实现后填写文件、函数和行号 |
| F20 | 待填写;涉及新规则时追加 R 编号 | 待实现 | 待实现 | 实现后填写文件、函数和行号 |
后续开发顺序:核对实时 `/_list` 与公开书单成员能力 → 服务端鉴权及只读接口 → 替换 Demo 数据 → 校验分页与字段口径 → 正式目标持久化。每次只将已实际验收的范围标为完成。
+28 -7
View File
@@ -68,7 +68,7 @@ node --check web-demo/demo-data.js
沿用 `reading_api.py` 的 `WEREAD_DB_HOST`、`WEREAD_DB_PORT`、`WEREAD_DB_USER`、`WEREAD_DB_PASSWORD` 和 `WEREAD_DB_NAME` 配置,不新增依赖。服务仍使用当前个人账号的数据;多个浏览器连接同一服务时共享目标,没有新增多用户隔离。
部署新代码前,在项目根目录执行一次迁移。下面的命令复用服务配置,只创建两张目标表,不修改已有业务表:
手动部署新代码时,在项目根目录按文件名顺序执行迁移。下面命令复用服务配置,幂等创建项目迁移定义的表:
```sh
python3 - <<'PY'
@@ -78,19 +78,40 @@ from reading_api import MYSQL_CONFIG
with pymysql.connect(**MYSQL_CONFIG) as conn:
with conn.cursor() as cur:
for sql in Path('migrations/001_reading_goals.sql').read_text().split(';'):
if sql.strip():
cur.execute(sql)
print('阅读目标表已就绪')
for migration in sorted(Path('migrations').glob('*.sql')):
for sql in migration.read_text(encoding='utf-8').split(';'):
if sql.strip():
cur.execute(sql)
print('数据库迁移已完成')
PY
PORT=8080 python3 app.py
```
使用已安装 `requirements.txt` 的 Python 环境。若 8080 已有本项目实例,先停止旧实例再启动,使 Flask 加载新接口。迁移可重复执行;运行账户需要目标表的读写权限。目标表不参与 `fetch_data.py` 的采集覆盖。
## 网站登录认证
正式页面和所有 `/api/*` 接口(包括 `/api/reading/*`、旧 `/api/v2/*` 与健康检查)都要求登录。未登录访问页面会跳转 `/login`;未登录直接请求 API 返回 401。CSS、JavaScript 等静态资源不包含个人数据,可公开加载。登录变更请求使用 CSRF token。
账号保存在 MySQL 的 `weread_auth` 表,密码以 Werkzeug 单向哈希存储。部署脚本会按文件名顺序执行 `migrations/*.sql`,其中 `002_site_auth.sql` 创建账号表。表中只允许一个管理员账号。
服务启动需要配置稳定的 `WEREAD_SESSION_SECRET`,用于签名会话;会话固定有效 5 天,Cookie 设置为 HttpOnly、SameSite=Lax,且 `WEREAD_SESSION_COOKIE_SECURE` 默认开启。生产环境必须通过 HTTPS 访问;仅本机 HTTP 预览时可设为 `WEREAD_SESSION_COOKIE_SECURE=0`。
首次部署还需在 systemd 环境中临时配置 `WEREAD_AUTH_SETUP_TOKEN`。可在服务器生成两个独立随机值:
```sh
python3 -c 'import secrets; print(secrets.token_urlsafe(48))'
```
将一个结果配置为 `WEREAD_SESSION_SECRET`,另一个配置为 `WEREAD_AUTH_SETUP_TOKEN`,重启服务后访问 `/login`。数据库尚无账号时,页面要求一次性口令、管理员用户名和至少 12 位密码;成功后只将密码哈希写入 MySQL,并关闭当前进程的初始化入口。然后从 systemd 配置中移除 `WEREAD_AUTH_SETUP_TOKEN` 并再次重启。若数据库已有账号,初始化表单不会开放。
不要把上述值写入 Git、浏览器代码或公开聊天。初始化口令不能省略:没有它,任何公网访问者都可能抢先创建管理员账号。当前应用不提供密码找回入口;需由管理员在服务器侧重置数据库中的哈希。
应用本身未实现登录尝试限流。公网部署还应在 Nginx 或其他可信入口对 `POST /auth/login` 配置频率限制,不能信任客户端自行传入的转发 IP。
## GitHub 自动部署
服务器部署使用根目录的 `deploy.sh`。脚本会先拉取指定分支,再检测 Python 环境;若项目 `.venv` 不存在,会自动用系统 `python3` 或 `python` 创建 `.venv`,随后安装 `requirements.txt`、执行阅读目标迁移,并重启 systemd 服务。
服务器部署使用根目录的 `deploy.sh`。脚本会先拉取指定分支,再检测 Python 环境;若项目 `.venv` 不存在,会自动用系统 `python3` 或 `python` 创建 `.venv`,随后安装 `requirements.txt`、按顺序执行 `migrations/*.sql`,并重启 systemd 服务。
当前服务器配置可按下列默认值使用:
@@ -150,6 +171,6 @@ node --check static/js/weapi.js
## master 自动部署
`master` 分支 push 后由 `.github/workflows/deploy.yml` 触发部署。Actions 通过仓库 Secrets 登录服务器,在 `DEPLOY_PATH` 指定的目录先拉取 `origin/master`,再执行根目录 `deploy.sh`,完成加载可选 `.env`、安装 Python 依赖、执行阅读目标表迁移和重启 systemd 服务。`dev` 和其他分支不会触发部署,部署脚本也会拒绝非 `master` 分支。详细配置见 [master 自动部署](deploy-master.md)。
`master` 分支 push 后由 `.github/workflows/deploy.yml` 触发部署。Actions 通过仓库 Secrets 登录服务器,在 `DEPLOY_PATH` 指定的目录先拉取 `origin/master`,再执行根目录 `deploy.sh`,完成加载可选 `.env`、安装 Python 依赖、执行全部数据库迁移和重启 systemd 服务。`dev` 和其他分支不会触发部署,部署脚本也会拒绝非 `master` 分支。详细配置见 [master 自动部署](deploy-master.md)。
本次数据库验收已在 8080 正式看板完成:页面新建目标后刷新仍可读取,编辑后再次刷新仍可读取,并已清理临时验收目标。读取目标列表未完成时,新建和编辑入口只显示目标页的加载或错误状态,不会打开保存表单。
+8
View File
@@ -18,6 +18,14 @@
## 快速排查
### 登录与会话
- 登录页返回 503:检查 systemd 是否配置 `WEREAD_SESSION_SECRET`,并确认数据库迁移已创建 `weread_auth` 表。
- 首次设置提示初始化口令无效:确认 `WEREAD_AUTH_SETUP_TOKEN` 已在服务环境中配置;账号创建成功后应从服务配置移除该值。
- 输入正确密码后仍回到登录页:公网必须通过 HTTPS 访问,检查 Cookie 的 `Secure` 标记与 `WEREAD_SESSION_COOKIE_SECURE` 设置;本机 HTTP 预览才设为 `0`。
- 页面可打开但 API 返回 401:登录 Cookie 未随请求发送,先重新登录并确认页面与 API 使用同一主机名和协议。
- 登录后写操作返回 CSRF 错误:刷新页面获取新的 token 后重试,不要跨域代理前端与 API。
页面出现“请求失败”时,按下面的顺序检查。
### 检查监听端口