From 1d886ce68de7e09f666daa9b64481d06812aec25 Mon Sep 17 00:00:00 2001 From: OpenClaw Agent Date: Sat, 7 Feb 2026 00:55:46 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E7=BA=AF=20Token=20?= =?UTF-8?q?=E8=AE=A4=E8=AF=81=E6=A8=A1=E5=BC=8F=20(Token=20Only=20Mode)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 TOKEN_ONLY_MODE 配置项,支持跳过 HMAC 签名验证 - 纯 Token 模式下只验证 token 参数,简化配置 - 添加 TOKEN_ONLY_MODE.md 详细文档 - 设置页面显示当前模式状态 - 更新 README.md 说明新功能 - config.example.json 添加 token_only_mode 配置项 适用于 TranspondSms 不支持签名的场景。 --- README.md | 1 + TOKEN_ONLY_MODE.md | 237 +++++++++++++++++++++++++++++++++++++++++++ app.py | 42 ++++++-- config.example.json | 3 +- config.py | 3 + templates/base.html | 93 +++++++++++++++++ templates/index.html | 4 + 7 files changed, 376 insertions(+), 7 deletions(-) create mode 100644 TOKEN_ONLY_MODE.md create mode 100644 templates/base.html diff --git a/README.md b/README.md index f608ee4..dc0d53b 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ - ✅ **Token/Secret 可选配置** - 支持在 config.json 中配置多个 API Token - ✅ **接收 Android APP 转发的短信** - POST multipart/form-data - ✅ **HMAC-SHA256 签名验证** - 可选的安全机制 +- ✅ **纯 Token 认证模式** - 跳过签名验证,只验证 token(查看 [TOKEN_ONLY_MODE.md](TOKEN_ONLY_MODE.md)) - ✅ **SQLite 数据库存储** - ✅ **Web 管理界面** - 查看实时短信、日志、统计 - ✅ **时区支持** - 自动转换为本地时间(默认 Asia/Shanghai) diff --git a/TOKEN_ONLY_MODE.md b/TOKEN_ONLY_MODE.md new file mode 100644 index 0000000..92ce3bf --- /dev/null +++ b/TOKEN_ONLY_MODE.md @@ -0,0 +1,237 @@ +# 纯 Token 模式配置指南 + +## 功能说明 + +纯 Token 模式(Token Only Mode)允许短信转发接收端**只验证 token 参数**,跳过 HMAC-SHA256 签名验证。 + +### 适用场景 + +- TranspondSms APP 不支持或不需要 HMAC 签名 +- 快速测试/开发环境 +- 简化配置,降低部署复杂度 + +### 安全性建议 + +- **生产环境建议使用标准模式**(Token + HMAC 签名) +- 纯 Token 模式下,请确保网络环境可信(内网、VPN) +- 定期更换 token + +--- + +## 配置方法 + +### 1. 编辑 `config.json` + +```json +{ + "security": { + "enabled": true, + "username": "admin", + "password": "admin123", + "session_lifetime": 3600, + "secret_key": "default_secret_key_change_me", + "sign_verify": true, + "sign_max_age": 3600000, + "token_only_mode": true // 启用纯 Token 模式 + }, + "api_tokens": [ + { + "name": "测试设备", + "token": "my_simple_token_123", + "secret": "", + "enabled": true + } + ] +} +``` + +### 关键配置项 + +| 配置项 | 类型 | 说明 | +|--------|------|------| +| `token_only_mode` | Boolean | 是否启用纯 Token 模式(默认 `false`) | +| `token` | String | Token 值(TranspondSms 发送的值) | +| `secret` | String | 在纯 Token 模式下可以为空 | + +### 2. 重启服务 + +```bash +./sms_receiverctl.sh restart +``` + +或使用 systemd: + +```bash +systemctl restart sms_receiver +``` + +--- + +## TranspondSms 配置 + +### 标准 Token + 签名模式(默认) + +TranspondSms 配置: + +``` +Token (URL): http://your-server:9518/api/receive?token=my_token +Secret: my_secret_key +``` + +### 纯 Token 模式 + +TranspondSms 配置: + +``` +Token (URL): http://your-server:9518/api/receive?token=my_simple_token_123 +Secret: (留空或随意) +``` + +**注意**:TranspondSms 仍会发送签名参数,但服务端会忽略签名验证,只验证 token 是否匹配。 + +--- + +## API 行为对比 + +### 标准 Token + 签名模式 + +1. TranspondSms 发送请求: + - `from`: 发送方号码 + - `content`: 短信内容 + - `timestamp`: 时间戳 + - `sign`: HMAC-SHA256 签名 + - `token`: 识别设备 + +2. 服务端验证: + - 根据 token 查找对应的 secret + - 验证 HMAC 签名是否匹配 + - 验证时间戳是否过期 + - 保存短信记录 + +### 纯 Token 模式 + +1. TranspondSms 发送请求(同样参数,但签名被忽略): + - `from`: 发送方号码 + - `content`: 短信内容 + - `token`: 识别设备 + +2. 服务端验证: + - **只验证 token 是否存在于配置列表中** + - **跳过签名验证** + - **跳过时间戳验证** + - 保存短信记录(标记为 `sign_verified: true`) + +--- + +## 错误处理 + +### Token 无效(纯 Token 模式) + +```json +{ + "error": "Token 无效" +} +``` + +返回:HTTP 401 + +### 缺少 Token(纯 Token 模式) + +```json +{ + "error": "缺少 token 参数(纯 Token 模式已启用)" +} +``` + +返回:HTTP 401 + +--- + +## 日志记录 + +服务会在日志中记录 Token 验证结果: + +``` +Token 验证成功: my_simple_token_123 +收到短信: 10086 -> 【移动】验证码是 123456... (ID: 1) +``` + +--- + +## 切换回标准模式 + +将 `token_only_mode` 设置为 `false` 并重启服务: + +```json +{ + "security": { + "token_only_mode": false + } +} +``` + +```bash +./sms_receiverctl.sh restart +``` + +--- + +## 常见问题 + +### Q: 纯 Token 模式安全吗? + +A: 相比标准模式,纯 Token 模式安全性较低(没有签名保护)。建议在内网环境或使用 VPN 的场景下使用。 + +### Q: 纯 Token 模式下还需要配置 secret 吗? + +A: 不需要。`secret` 字段可以为空。 + +### Q: 可以混合使用两种模式吗? + +A: 不可以。一个服务实例要么是标准模式,要么是纯 Token 模式。 + +### Q: TranspondSms 不发送签名会怎样? + +A: 即使配置了标准模式,如果 TranspondSms 不发送签名,请求会失败(HTTP 403)。此时请使用纯 Token 模式。 + +--- + +## 代码变更 + +### 配置文件 (`config.py`) + +```python +# 新增配置项 +TOKEN_ONLY_MODE = False +``` + +### 接收逻辑 (`app.py`) + +```python +if app.config['TOKEN_ONLY_MODE']: + # 纯 Token 模式:只验证 token + if not token_param: + return jsonify({'error': '缺少 token 参数'}), 401 + + # 查找 token 并验证 + for token_config in app.config['API_TOKENS']: + if token_config.get('token') == token_param: + token_valid = True + break + + if not token_valid: + return jsonify({'error': 'Token 无效'}), 401 +else: + # 标准 Token + 签名模式 + # ... 原有逻辑 +``` + +--- + +## 相关文件 + +- `config.py` - 配置类定义 +- `config.json` - 配置文件 +- `app.py` - 主应用逻辑 +- `settings.html` - 设置页面 +- `TOKEN_ONLY_MODE.md` - 本文档 diff --git a/app.py b/app.py index 869167f..8a7e5ec 100644 --- a/app.py +++ b/app.py @@ -57,6 +57,7 @@ def create_app(config_name='default'): app.config['LOGIN_USERNAME'] = app_config.LOGIN_USERNAME app.config['LOGIN_PASSWORD'] = app_config.LOGIN_PASSWORD app.config['SESSION_LIFETIME'] = app_config.SESSION_LIFETIME + app.config['TOKEN_ONLY_MODE'] = app_config.TOKEN_ONLY_MODE # 初始化日志 setup_logging(app) @@ -248,9 +249,11 @@ def register_routes(app, db): def settings(): """设置页面""" return render_template('settings.html', + app={'name': '短信转发接收端'}, api_tokens=app.config['API_TOKENS'], login_enabled=app.config['LOGIN_ENABLED'], - sign_verify=app.config['SIGN_VERIFY']) + sign_verify=app.config['SIGN_VERIFY'], + token_only_mode=app.config['TOKEN_ONLY_MODE']) @app.route('/api/receive', methods=['POST']) def receive_sms(): @@ -270,14 +273,37 @@ def register_routes(app, db): 'error', '缺少必填参数 (from/content)') return jsonify({'error': '缺少必填参数'}), 400 - # 如果提供了 token,查找对应的 secret + # Token 验证 secret = None - if token_param: + token_valid = False + + if app.config['TOKEN_ONLY_MODE']: + # 纯 Token 模式:只验证 token,不需要签名 + if not token_param: + db.add_log(from_number, content, None, sign, None, ip_address, + 'error', '缺少 token 参数(纯 Token 模式已启用)') + return jsonify({'error': '缺少 token 参数'}), 401 + for token_config in app.config['API_TOKENS']: if token_config.get('enabled') and token_config.get('token') == token_param: - secret = token_config.get('secret') + token_valid = True + secret = token_config.get('secret', '') + app.logger.info(f'Token 验证成功: {token_param}') break + if not token_valid: + db.add_log(from_number, content, None, sign, None, ip_address, + 'error', f'Token 无效: {token_param}') + return jsonify({'error': 'Token 无效'}), 401 + + else: + # 标准 Token + 签名模式 + if token_param: + for token_config in app.config['API_TOKENS']: + if token_config.get('enabled') and token_config.get('token') == token_param: + secret = token_config.get('secret') + break + # 解析时间戳 timestamp = None if timestamp_str: @@ -288,9 +314,9 @@ def register_routes(app, db): 'error', f'时间戳格式错误: {timestamp_str}') return jsonify({'error': '时间戳格式错误'}), 400 - # 验证签名 + # 验证签名(仅在非纯 Token 模式且提供了 secret 时) sign_verified = False - if sign and secret and app.config['SIGN_VERIFY']: + if not app.config['TOKEN_ONLY_MODE'] and sign and secret and app.config['SIGN_VERIFY']: is_valid, message = verify_from_app( from_number, content, timestamp, sign, secret, app.config['SIGN_MAX_AGE'] @@ -305,6 +331,10 @@ def register_routes(app, db): sign_verified = True app.logger.info(f'短信已签名验证: {from_number}') + # 纯 Token 模式下,token 验证成功则视为 sign_verified + if app.config['TOKEN_ONLY_MODE'] and token_valid: + sign_verified = True + # 保存短信 message_id = db.add_message( from_number=from_number, diff --git a/config.example.json b/config.example.json index d5f5819..6cdb0ae 100644 --- a/config.example.json +++ b/config.example.json @@ -15,7 +15,8 @@ "session_lifetime": 3600, "secret_key": "default_secret_key_change_me", "sign_verify": true, - "sign_max_age": 3600000 + "sign_max_age": 3600000, + "token_only_mode": false }, "sms": { "max_messages": 10000, diff --git a/config.py b/config.py index 1663585..1461234 100644 --- a/config.py +++ b/config.py @@ -26,6 +26,9 @@ class Config: LOGIN_PASSWORD = 'admin123' SESSION_LIFETIME = 3600 # 会话有效期(秒),默认1小时 + # 纯 Token 模式配置 + TOKEN_ONLY_MODE = False # 纯 Token 模式:只验证 token,跳过 HMAC 签名验证 + # 数据库配置 BASE_DIR = os.path.dirname(os.path.abspath(__file__)) DATABASE_PATH = os.path.join(BASE_DIR, 'sms_receiver.db') diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..1a10eed --- /dev/null +++ b/templates/base.html @@ -0,0 +1,93 @@ +{% extends "base.html" %} + +{% block title %}设置 - {{ config.app.name }}{% endblock %} + +{% block content %} +
+
+
+
+
+

系统设置

+
+
+
当前配置
+ + + + + + + + + + + + + + + + +
登录验证{{ '启用' if login_enabled else '禁用' }}
纯 Token 模式 + {% if token_only_mode %} + 启用 +
只验证 token,跳过 HMAC 签名验证 + {% else %} + 禁用 +
标准模式:Token + HMAC 签名验证 + {% endif %} +
HMAC 签名验证{{ '启用' if sign_verify else '禁用' }}
+ +
+ +
API Token 配置
+ +
+ + + + + + + + + + {% for token_item in api_tokens %} + + + + + + {% endfor %} + +
名称Token状态
{{ token_item.get('name', '未命名') }}{{ token_item.get('token', '') }} + {% if token_item.get('enabled') %} + 启用 + {% else %} + 禁用 + {% endif %} +
+
+ +
+
配置说明
+

+ 要修改这些设置,请编辑 config.json 文件后重启服务。
+ 纯 Token 模式:TranspondSms 只需要发送 token 参数即可,签名验证会被跳过。 + 适用于不使用 HMAC 签名的场景。 +

+
+ +
+
模式切换
+

+ 在 config.json 中设置 "security.token_only_mode": true 启用纯 Token 模式。
+ 修改后需要重启服务:./sms_receiverctl.sh restart +

+
+
+
+
+
+
+{% endblock %} diff --git a/templates/index.html b/templates/index.html index bf30fb2..12597d1 100644 --- a/templates/index.html +++ b/templates/index.html @@ -54,6 +54,10 @@ transition: background 0.3s; } + .nav a.active { + background: #764ba2; + } + .nav a:hover { background: #764ba2; }