Sync from upstream/ai-dev (squashed)

pull/2/head
alimu 2 weeks ago
parent 4ea64e8f8a
commit fc6ebccb59

4
.gitignore vendored

@ -6,3 +6,7 @@ web-client/dist/
web-client/.vite web-client/.vite
deploy/certs/ deploy/certs/
deploy/keys/ deploy/keys/
# macOS metadata
.DS_Store
**/.DS_Store

@ -1,45 +1,47 @@
# OnlineMsgServer # OnlineMsgServer
在线消息中转服务WebSocket + RSA支持客户端鉴权、单播转发、广播、签名校验、防重放限流。 在线消息中转服务WebSocket + RSA支持客户端鉴权、单播转发、广播、签名校验、防重放限流。
## 当前默认行为 ## 仓库结构
- 服务端默认 `REQUIRE_WSS=false`(便于内外网测试) - `deploy/`:一键部署与生产产物脚本
- 仍支持 `WSS(TLS)`,可通过环境变量启用 - `web-client/`React Web 客户端
- 客户端需要完成 challenge-response 鉴权后才能发业务消息 - `android-client/`AndroidKotlin + Compose客户端
## 运行前提
- `.NET 8 SDK`
- `Docker`
- `openssl`
- 部署脚本 `deploy/deploy_test_ws.sh``deploy/redeploy_with_lan_cert.sh` 依赖 `ipconfig`、`route`(当前按 macOS 环境编写)
## 快速开始 ## 快速开始
### 1) 测试模式(推荐先跑通) 先进入仓库根目录:
```bash
cd <repo-root>
```
### 1) 测试模式WS
```bash ```bash
cd /Users/solux/Codes/OnlineMsgServer
bash deploy/deploy_test_ws.sh bash deploy/deploy_test_ws.sh
``` ```
这个脚本会自动: 脚本会自动生成/复用协议私钥、构建镜像并以 `REQUIRE_WSS=false` 启动容器。
- 生成/复用服务端 RSA 私钥(`deploy/keys`
- 构建镜像并重启容器
- 以 `REQUIRE_WSS=false` 启动服务
- 输出可直接使用的 `ws://` 地址
### 2) 安全模式WSS + 局域网证书) ### 2) 安全模式WSS + 局域网证书)
```bash ```bash
cd /Users/solux/Codes/OnlineMsgServer
bash deploy/redeploy_with_lan_cert.sh bash deploy/redeploy_with_lan_cert.sh
``` ```
这个脚本会自动: 脚本会重签包含当前局域网 IP 的证书、构建镜像并以 `REQUIRE_WSS=true` 启动容器。
- 重新生成带当前 LAN IP 的 TLS 证书(`deploy/certs`
- 构建镜像并重启容器
- 以 `REQUIRE_WSS=true` 启动服务
- 输出可直接使用的 `wss://` 地址
### 3) 生产准备(证书 + 镜像 + 部署产物) ### 3) 生产准备(证书 + 镜像 + 部署产物)
```bash ```bash
cd /Users/solux/Codes/OnlineMsgServer
DOMAIN=chat.example.com \ DOMAIN=chat.example.com \
TLS_CERT_PEM=/path/fullchain.pem \ TLS_CERT_PEM=/path/fullchain.pem \
TLS_KEY_PEM=/path/privkey.pem \ TLS_KEY_PEM=/path/privkey.pem \
@ -48,16 +50,16 @@ CERT_PASSWORD='change-me' \
bash deploy/prepare_prod_release.sh bash deploy/prepare_prod_release.sh
``` ```
脚本会自动: 输出目录默认在 `deploy/output/prod`,包含 `prod.env`、镜像 tar可选和运行示例脚本。
- 准备服务端协议私钥(`deploy/keys`
- 生成运行时 `server.pfx``deploy/certs`
- 构建生产镜像(默认 `onlinemsgserver:prod`
- 导出部署产物到 `deploy/output/prod``prod.env`、镜像 tar、运行示例脚本
如果你暂时没有 CA 证书,也可用自签名兜底(仅测试): 无 CA 证书时可临时使用自签名(仅测试):
```bash ```bash
DOMAIN=chat.example.com SAN_LIST='DNS:www.chat.example.com,IP:10.0.0.8' GENERATE_SELF_SIGNED=true bash deploy/prepare_prod_release.sh DOMAIN=chat.example.com \
SAN_LIST='DNS:www.chat.example.com,IP:10.0.0.8' \
GENERATE_SELF_SIGNED=true \
CERT_PASSWORD='change-me' \
bash deploy/prepare_prod_release.sh
``` ```
## 手动 Docker 启动示例 ## 手动 Docker 启动示例
@ -67,19 +69,19 @@ DOMAIN=chat.example.com SAN_LIST='DNS:www.chat.example.com,IP:10.0.0.8' GENERATE
```bash ```bash
docker run -d --name onlinemsgserver --restart unless-stopped \ docker run -d --name onlinemsgserver --restart unless-stopped \
-p 13173:13173 \ -p 13173:13173 \
-v /Users/solux/Codes/OnlineMsgServer/deploy/keys:/app/keys:ro \ -v "$(pwd)/deploy/keys:/app/keys:ro" \
-e REQUIRE_WSS=false \ -e REQUIRE_WSS=false \
-e SERVER_PRIVATE_KEY_PATH=/app/keys/server_rsa_pkcs8.b64 \ -e SERVER_PRIVATE_KEY_PATH=/app/keys/server_rsa_pkcs8.b64 \
onlinemsgserver:latest onlinemsgserver:latest
``` ```
### WSS生产/生产) ### WSS生产/生产)
```bash ```bash
docker run -d --name onlinemsgserver --restart unless-stopped \ docker run -d --name onlinemsgserver --restart unless-stopped \
-p 13173:13173 \ -p 13173:13173 \
-v /Users/solux/Codes/OnlineMsgServer/deploy/certs:/app/certs:ro \ -v "$(pwd)/deploy/certs:/app/certs:ro" \
-v /Users/solux/Codes/OnlineMsgServer/deploy/keys:/app/keys:ro \ -v "$(pwd)/deploy/keys:/app/keys:ro" \
-e REQUIRE_WSS=true \ -e REQUIRE_WSS=true \
-e TLS_CERT_PATH=/app/certs/server.pfx \ -e TLS_CERT_PATH=/app/certs/server.pfx \
-e TLS_CERT_PASSWORD=changeit \ -e TLS_CERT_PASSWORD=changeit \
@ -87,16 +89,16 @@ docker run -d --name onlinemsgserver --restart unless-stopped \
onlinemsgserver:latest onlinemsgserver:latest
``` ```
## 协议说明(客户端 -> 服务端) ## 协议说明
### 加密方式 ### 加密方式
- RSA-2048-OAEP-SHA256 - RSA-2048-OAEP-SHA256
- 明文分块 190 字节加密 - 明文按 190 字节分块加密
- 密文按 256 字节分块解密 - 密文按 256 字节分块解密
- 传输格式为 base64 字符串 - 业务消息传输为 base64 字符串
### 通用包结构 ### 通用包结构(客户端 -> 服务端)
```json ```json
{ {
@ -106,15 +108,29 @@ docker run -d --name onlinemsgserver --restart unless-stopped \
} }
``` ```
### 1) 鉴权登记 `type=publickey` ### 连接首包(服务端 -> 客户端,明文)
- `key`:用户名(可空) ```json
{
"type": "publickey",
"data": {
"publicKey": "服务端公钥(base64 SPKI)",
"authChallenge": "一次性挑战值",
"authTtlSeconds": 120,
"certFingerprintSha256": "TLS证书指纹(启用WSS时)"
}
}
```
### 鉴权登记 `type=publickey`(客户端 -> 服务端)
- `key`:用户名(为空时服务端会生成匿名名)
- `data` - `data`
```json ```json
{ {
"publicKey": "客户端公钥(base64 SPKI)", "publicKey": "客户端公钥(base64 SPKI)",
"challenge": "服务端下发挑战值", "challenge": "上一步 authChallenge",
"timestamp": 1739600000, "timestamp": 1739600000,
"nonce": "随机字符串", "nonce": "随机字符串",
"signature": "签名(base64)" "signature": "签名(base64)"
@ -127,9 +143,9 @@ docker run -d --name onlinemsgserver --restart unless-stopped \
publickey\n{userName}\n{publicKey}\n{challenge}\n{timestamp}\n{nonce} publickey\n{userName}\n{publicKey}\n{challenge}\n{timestamp}\n{nonce}
``` ```
### 2) 单播转发 `type=forward` ### 单播 `type=forward`
- `key`:目标公钥 - `key`:目标客户端公钥
- `data` - `data`
```json ```json
@ -147,9 +163,9 @@ publickey\n{userName}\n{publicKey}\n{challenge}\n{timestamp}\n{nonce}
forward\n{targetPublicKey}\n{payload}\n{timestamp}\n{nonce} forward\n{targetPublicKey}\n{payload}\n{timestamp}\n{nonce}
``` ```
### 3) 广播 `type=broadcast` ### 广播 `type=broadcast`
- `key`:可空 - `key`:可字符串
- `data`:同 `forward` - `data`:同 `forward`
签名串: 签名串:
@ -160,43 +176,29 @@ broadcast\n{key}\n{payload}\n{timestamp}\n{nonce}
### 连接流程 ### 连接流程
1. 客户端连接后,服务端先返回未加密 `publickey`含服务端公钥、challenge、TTL、证书指纹 1. 客户端建立 WebSocket 连接后接收明文 `publickey` 首包
2. 客户端发送签名鉴权包(`type=publickey`)。 2. 客户端发送签名鉴权包(`type=publickey`)。
3. 鉴权成功后发送 `forward` / `broadcast` 业务 3. 鉴权成功后,客户端发送 `forward` / `broadcast` 业务消息(加密 + 签名)
## 环境变量 ## 环境变量
- `LISTEN_PORT`:监听端口(默认 `13173` - `LISTEN_PORT`:监听端口,默认 `13173`
- `REQUIRE_WSS`:是否启用 WSS(默认 `false` - `REQUIRE_WSS`:是否启用 WSS,默认 `false`
- `TLS_CERT_PATH`证书路径WSS 必填) - `TLS_CERT_PATH`:证书路径(启用 WSS 必填)
- `TLS_CERT_PASSWORD`:证书密码(可空) - `TLS_CERT_PASSWORD`:证书密码(可空)
- `SERVER_PRIVATE_KEY_B64`服务端私钥PKCS8 base64 - `SERVER_PRIVATE_KEY_B64`服务端私钥PKCS8 base64
- `SERVER_PRIVATE_KEY_PATH`:服务端私钥文件路径(与上面二选一) - `SERVER_PRIVATE_KEY_PATH`:服务端私钥文件路径(与上面二选一)
- `ALLOW_EPHEMERAL_SERVER_KEY`:允许仅内存临时私钥(默认 `false` - `ALLOW_EPHEMERAL_SERVER_KEY`:允许使用临时内存私钥,默认 `false`
- `MAX_CONNECTIONS`:最大连接数 - `MAX_CONNECTIONS`:最大连接数,默认 `1000`
- `MAX_MESSAGE_BYTES`:最大消息字节数 - `MAX_MESSAGE_BYTES`:单消息最大字节数,默认 `65536`
- `RATE_LIMIT_COUNT`:限流窗口内最大消息数 - `RATE_LIMIT_COUNT`:限流窗口允许消息数,默认 `30`
- `RATE_LIMIT_WINDOW_SECONDS`:限流窗口秒数 - `RATE_LIMIT_WINDOW_SECONDS`:限流窗口秒数,默认 `10`
- `IP_BLOCK_SECONDS`:触发滥用后 IP 封禁秒数 - `IP_BLOCK_SECONDS`:触发滥用后的封禁秒数,默认 `120`
- `CHALLENGE_TTL_SECONDS`challenge 有效期 - `CHALLENGE_TTL_SECONDS`:挑战值有效期秒数,默认 `120`
- `MAX_CLOCK_SKEW_SECONDS`:允许时钟偏差 - `MAX_CLOCK_SKEW_SECONDS`:允许时钟偏差秒数,默认 `60`
- `REPLAY_WINDOW_SECONDS`:防重放窗口 - `REPLAY_WINDOW_SECONDS`:防重放窗口秒数,默认 `120`
## 前端React ## 客户端文档
前端目录:`/Users/solux/Codes/OnlineMsgServer/web-client` - Web 客户端说明:`web-client/README.md`
- Android 客户端说明:`android-client/README.md`
```bash
cd /Users/solux/Codes/OnlineMsgServer/web-client
npm install
npm run dev
```
当前前端能力:
- 默认隐藏协议细节,手动地址放在“高级连接设置”
- 支持广播/私聊、查看并复制自己的公钥
- 每条消息支持一键复制
- 自动处理超长消息换行,不溢出消息框
- 用户名和客户端私钥本地持久化,刷新后继续使用
更多前端说明见 `web-client/README.md`

@ -2,7 +2,7 @@
本目录是针对当前 `OnlineMsgServer` 协议实现的 Android 客户端。 本目录是针对当前 `OnlineMsgServer` 协议实现的 Android 客户端。
## 已实现能力 ## 主要能力
- Kotlin + Jetpack Compose + Material3 - Kotlin + Jetpack Compose + Material3
- 与当前服务端协议兼容: - 与当前服务端协议兼容:
@ -34,7 +34,14 @@
2. 等待 Gradle Sync 完成。 2. 等待 Gradle Sync 完成。
3. 运行 `app` 3. 运行 `app`
## 联调建议 命令行构建示例:
```bash
cd android-client
./gradlew assembleDebug
```
## 联调地址建议
- 模拟器建议地址:`ws://10.0.2.2:13173/` - 模拟器建议地址:`ws://10.0.2.2:13173/`
- 真机建议地址:`ws://<你的局域网IP>:13173/` - 真机建议地址:`ws://<你的局域网IP>:13173/`
@ -49,6 +56,14 @@
- `forward``key` 必须是目标公钥。 - `forward``key` 必须是目标公钥。
- `broadcast``key` 为空字符串。 - `broadcast``key` 为空字符串。
## 构建产物导出(可选)
- `assembleDebug` 结束后会触发 `exportDebugApk` 任务,把 `app-debug.apk` 复制到导出目录。
- 默认导出目录:`android-client/app/build/exports/apk-debug`
- 可通过以下方式覆盖导出目录:
- Gradle 属性:`-PdebugApkExportDir=/your/path`
- 环境变量:`DEBUG_APK_EXPORT_DIR=/your/path`
## 已知限制 ## 已知限制
- 当前未内置证书固定pinning如用于公网生产建议额外启用证书固定策略。 - 当前未内置证书固定pinning如用于公网生产建议额外启用证书固定策略。

@ -83,7 +83,10 @@ dependencies {
androidTestImplementation("androidx.compose.ui:ui-test-junit4") androidTestImplementation("androidx.compose.ui:ui-test-junit4")
} }
val debugApkExportDir = "/Users/solux/Docker/webdav/share/public/apk-release" val debugApkExportDir: String = providers.gradleProperty("debugApkExportDir")
.orElse(providers.environmentVariable("DEBUG_APK_EXPORT_DIR"))
.orElse(layout.buildDirectory.dir("exports/apk-debug").map { it.asFile.absolutePath })
.get()
val debugApkExportName = "onlinemsgclient-debug.apk" val debugApkExportName = "onlinemsgclient-debug.apk"
val exportDebugApk by tasks.registering(Copy::class) { val exportDebugApk by tasks.registering(Copy::class) {

@ -1,6 +1,6 @@
# OnlineMsg Web Client # OnlineMsg Web Client
React 前端客户端,适配当前仓库的加密消息协议,默认隐藏连接细节,仅保留聊天交互。 React 前端客户端,适配当前仓库消息协议,默认隐藏协议细节并聚焦聊天交互。
## 开发运行 ## 开发运行
@ -25,17 +25,16 @@ npm run preview
## 使用说明 ## 使用说明
- 打开页面后点击“连接” 1. 打开页面后点击“连接”。
- 默认会自动使用当前主机名拼接: 2. 默认服务器地址会根据当前页面协议和主机自动推断:
- `http` 页面下:`ws://<host>:13173/` - 当页面是 `https` 且主机不是本机地址时:`wss://<host>/msgws/`
- `https` 页面下:`wss://<host>:13173/` - 其他情况:`ws://<host>:13173/`
- 若你手动输入 `ws://`,前端会自动尝试升级到 `wss://` 一次 3. 若首连失败且当前地址是 `ws://`,客户端会自动切换到 `wss://` 重试 1 次。
- 如需手动指定服务器地址,在“高级连接设置”中填写,例如: 4. 如需手动指定服务器地址,在“高级连接设置”中填写,例如:
- `wss://example.com:13173/` - `wss://example.com/msgws/`
- `ws://127.0.0.1:13173/`(仅本地调试) - `ws://127.0.0.1:13173/`(本地调试)
- “目标公钥”留空为广播,填写后为私聊转发 5. “目标公钥”为空时发送广播,填写后发送私聊(`forward`)。
- 用户名会自动保存在本地,刷新后继续使用 6. 用户名、服务器地址历史、客户端私钥会保存在浏览器本地存储中。
- 客户端私钥会保存在本地浏览器(用于持续身份),刷新后不会重复生成
## 移动端注意事项 ## 移动端注意事项

Loading…
Cancel
Save