统一认证中心 + App 设备认证 + 消息盒子 + 管理端 API。
- Backend:
http://localhost:8080 - Frontend(Admin):
http://localhost:8081/admin/- 默认管理入口页面:
/admin/accounts(旧/admin/users、/admin/clients会自动跳转)
- 默认管理入口页面:
docker compose up --buildrelease-scripts/mac/package.sh 构建时要求项目根目录存在 .env.example。
- 若存在
.env:优先加载.env作为构建变量。 - 若不存在
.env:回退加载.env.example。
运行时 release/.env 由安装流程(setup)或运维提供,release-scripts/mac/package.sh 不负责写入运行时 env。
release-scripts/mac/:macOS / Linux 使用的 shell 脚本(.sh)release-scripts/windows/:Windows 使用的脚本目录(.ps1/.bat)
构建命令:
./release-scripts/mac/package.sh- 不再内置默认密码。
- 部署运行时必须在
release/.env中设置:AUTH_ADMIN_PASSWORD_BCRYPTAUTH_APP_MASTER_PASSWORD_BCRYPT
- 下文示例里使用
password仅用于演示,请替换为你的实际密码。
# macOS
htpasswd -nbBC 10 '' 'your-password' | cut -d: -f2- OAuth2:
/oauth2/* - OIDC:
/openid/* - App Auth:
/api/auth/* - App Inbox:
/api/app/* - Admin:
/admin/api/*
AUTH_DB_PATH- SQLite 文件路径(默认
../data/auth.db,对应项目根目录data/auth.db)。 - JWK 密钥也在这个库里,表
JWK_KEY_的PRIVATE_KEY_列就是私钥(Base64 编码的 PKCS#8)。
- SQLite 文件路径(默认
AUTH_ISSUER(默认http://localhost:8080)- 作为 OAuth2/OIDC 的 issuer,写入
/.well-known元数据和令牌相关配置。 - 也用于 App Access Token 的
issclaim 签发与校验,不一致会导致 token 验证失败。
- 作为 OAuth2/OIDC 的 issuer,写入
AUTH_APP_INTERNAL_WEBHOOK_SECRET- 用于
/api/app/internal/chat-events的 HMAC-SHA256 签名校验。
- 用于
AUTH_TOKEN_ACCESS_TTL/AUTH_TOKEN_REFRESH_TTL/AUTH_TOKEN_ROTATE_REFRESH_TOKEN- 控制 OAuth2 客户端 access token / refresh token 生命周期与是否轮换 refresh token。
项目内置脚本:release-scripts/mac/manage-jwk-key.sh
- 首次安装(推荐):若库里无密钥则生成;若已有则导出
./release-scripts/mac/manage-jwk-key.sh --mode bootstrap --db ./data/auth.db --out ./data/keys- 强制轮换(生成新私钥并覆盖库中旧密钥)
./release-scripts/mac/manage-jwk-key.sh --mode rotate --db ./data/auth.db --out ./data/keys产物:
- 公钥:
./data/keys/jwk-public.pem(可给其他项目验签) - 私钥:
./data/keys/jwk-private.pem
注意:rotate 会导致旧 access token 验签失败,建议在维护窗口执行。
项目内置脚本:
- macOS / Linux:
release-scripts/mac/setup-jwk-public-key.sh - Windows:
release-scripts/windows/setup-jwk-public-key.ps1
功能:
- 先执行
manage-jwk-key.sh(bootstrap或rotate) - 导出
jwk-public.pem/jwk-private.pem - 额外导出一个
publicKey文件(默认:<out>/publicKey.pem),供其他项目直接读取或分发
示例(建议在你的独立 setup 脚本里调用):
./release-scripts/mac/setup-jwk-public-key.sh \
--mode bootstrap \
--db ./data/auth.db \
--out ./data/keys.\release-scripts\windows\setup-jwk-public-key.ps1 `
-Mode bootstrap `
-DbPath .\data\auth.db `
-OutDir .\data\keys执行后产物:
./data/keys/jwk-public.pem./data/keys/jwk-private.pem./data/keys/publicKey.pem(等同公钥副本,给其他项目使用)
以下命令默认在 macOS / Linux 下执行。
BASE_URL="http://localhost:11952"
BASE_URL建议指向后端8080。8081前端容器只代理/admin/api、/oauth2、/openid,不代理/api/auth、/api/app。
curl -sS "$BASE_URL/openid/.well-known/openid-configuration"
curl -sS "$BASE_URL/openid/.well-known/oauth-authorization-server"LOGIN_JSON="$(curl -sS -X POST "$BASE_URL/api/auth/login" \
-H "Content-Type: application/json" \
-d '{
"masterPassword":"password",
"deviceName":"MacBook-Pro",
"accessTtlSeconds":1800
}')"
echo "$LOGIN_JSON"
ACCESS_TOKEN="$(printf '%s' "$LOGIN_JSON" | sed -nE 's/.*"accessToken":"([^"]+)".*/\1/p')"
DEVICE_TOKEN="$(printf '%s' "$LOGIN_JSON" | sed -nE 's/.*"deviceToken":"([^"]+)".*/\1/p')"
DEVICE_ID="$(printf '%s' "$LOGIN_JSON" | sed -nE 's/.*"deviceId":"([^"]+)".*/\1/p')"
echo "ACCESS_TOKEN length: ${#ACCESS_TOKEN}"
echo "DEVICE_TOKEN length: ${#DEVICE_TOKEN}"
echo "DEVICE_ID: $DEVICE_ID"curl -sS "$BASE_URL/api/auth/me" \
-H "Authorization: Bearer $ACCESS_TOKEN"
curl -sS "$BASE_URL/api/auth/devices" \
-H "Authorization: Bearer $ACCESS_TOKEN"
curl -sS -X PATCH "$BASE_URL/api/auth/devices/$DEVICE_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"deviceName":"My-Mac"}'REFRESH_JSON="$(curl -sS -X POST "$BASE_URL/api/auth/refresh" \
-H "Content-Type: application/json" \
-d "{\"deviceToken\":\"$DEVICE_TOKEN\",\"accessTtlSeconds\":1200}")"
echo "$REFRESH_JSON"
NEW_ACCESS_TOKEN="$(printf '%s' "$REFRESH_JSON" | sed -nE 's/.*"accessToken":"([^"]+)".*/\1/p')"
NEW_DEVICE_TOKEN="$(printf '%s' "$REFRESH_JSON" | sed -nE 's/.*"deviceToken":"([^"]+)".*/\1/p')"
echo "NEW_ACCESS_TOKEN length: ${#NEW_ACCESS_TOKEN}"
echo "NEW_DEVICE_TOKEN length: ${#NEW_DEVICE_TOKEN}"
accessTtlSeconds为可选字段,不传时使用服务端默认值AUTH_APP_ACCESS_TTL(默认PT10M)。可申请的最大值受AUTH_APP_MAX_ACCESS_TTL限制(默认P30D)。
ADMIN_COOKIE_JAR="$(mktemp)"
curl -sS -X POST "$BASE_URL/admin/api/session/login" \
-c "$ADMIN_COOKIE_JAR" \
-H "Content-Type: application/json" \
-d '{
"username":"admin",
"password":"password"
}'
curl -sS "$BASE_URL/admin/api/session/me" \
-b "$ADMIN_COOKIE_JAR"SEND_JSON="$(curl -sS -X POST "$BASE_URL/admin/api/inbox/send" \
-b "$ADMIN_COOKIE_JAR" \
-H "Content-Type: application/json" \
-d '{
"title":"系统通知",
"content":"发布成功",
"type":"INFO",
"payload":{"source":"admin-console"}
}')"
echo "$SEND_JSON"
MESSAGE_ID="$(printf '%s' "$SEND_JSON" | sed -nE 's/.*"messageId":"([^"]+)".*/\1/p')"
echo "MESSAGE_ID: $MESSAGE_ID"
curl -sS "$BASE_URL/api/app/inbox/unread-count" \
-H "Authorization: Bearer $ACCESS_TOKEN"
curl -sS "$BASE_URL/api/app/inbox?limit=20" \
-H "Authorization: Bearer $ACCESS_TOKEN"curl -i -X POST "$BASE_URL/api/app/inbox/read" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"messageIds\":[\"$MESSAGE_ID\"]}"
curl -i -X POST "$BASE_URL/api/app/inbox/read-all" \
-H "Authorization: Bearer $ACCESS_TOKEN"
curl -sS "$BASE_URL/api/app/inbox/unread-count" \
-H "Authorization: Bearer $ACCESS_TOKEN"# 用户列表
curl -sS "$BASE_URL/admin/api/users" -b "$ADMIN_COOKIE_JAR"
# 创建用户
curl -sS -X POST "$BASE_URL/admin/api/users" \
-b "$ADMIN_COOKIE_JAR" \
-H "Content-Type: application/json" \
-d '{
"username":"u_demo",
"password":"pass_demo",
"displayName":"Demo User",
"status":"ACTIVE"
}'
# 客户端列表
curl -sS "$BASE_URL/admin/api/clients" -b "$ADMIN_COOKIE_JAR"
# 创建客户端
curl -sS -X POST "$BASE_URL/admin/api/clients" \
-b "$ADMIN_COOKIE_JAR" \
-H "Content-Type: application/json" \
-d '{
"clientId":"demo-client",
"clientName":"Demo Client",
"clientSecret":"",
"grantTypes":["authorization_code","refresh_token"],
"redirectUris":["myapp://oauthredirect"],
"scopes":["openid","profile"],
"requirePkce":true,
"status":"ACTIVE"
}'依赖
openssl。
WEBHOOK_SECRET="change-me"
TIMESTAMP="$(date +%s)"
BODY='{"chatId":"123e4567-e89b-12d3-a456-426614174111","runId":"123e4567-e89b-12d3-a456-426614174112","chatName":"Demo Chat","updatedAt":1739870000000}'
SIGNATURE="$(printf '%s' "$TIMESTAMP.$BODY" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" | sed 's/^.* //')"
echo "TIMESTAMP=$TIMESTAMP"
echo "SIGNATURE=$SIGNATURE"
curl -sS -X POST "$BASE_URL/api/app/internal/chat-events" \
-H "Content-Type: application/json" \
-H "X-App-Timestamp: $TIMESTAMP" \
-H "X-App-Signature: $SIGNATURE" \
-d "$BODY"curl -i -X POST "$BASE_URL/api/auth/logout" \
-H "Authorization: Bearer $ACCESS_TOKEN"
curl -i -X POST "$BASE_URL/admin/api/session/logout" \
-b "$ADMIN_COOKIE_JAR"
rm -f "$ADMIN_COOKIE_JAR"POST /api/auth/loginPOST /api/auth/refreshPOST /api/auth/logoutGET /api/auth/meGET /api/auth/devicesPATCH /api/auth/devices/{deviceId}DELETE /api/auth/devices/{deviceId}GET /api/auth/jwks
GET /api/app/inboxGET /api/app/inbox/unread-countPOST /api/app/inbox/readPOST /api/app/inbox/read-allPOST /api/app/internal/chat-eventsWS /api/app/ws
- Session
POST /admin/api/session/loginPOST /admin/api/session/logoutGET /admin/api/session/me
- Security Utility
POST /admin/api/bcrypt/generateGET /admin/api/security/jwksPOST /admin/api/security/public-key/generatePOST /admin/api/security/key-pair/generatePOST /admin/api/security/app-tokens/issuePOST /admin/api/security/app-tokens/refreshGET /admin/api/security/app-devicesPOST /admin/api/security/app-devices/{deviceId}/revokeGET /admin/api/security/tokens
- Users
GET /admin/api/usersPOST /admin/api/usersGET /admin/api/users/{userId}PUT /admin/api/users/{userId}PATCH /admin/api/users/{userId}/statusPOST /admin/api/users/{userId}/password
- Clients
GET /admin/api/clientsPOST /admin/api/clientsGET /admin/api/clients/{clientId}PUT /admin/api/clients/{clientId}PATCH /admin/api/clients/{clientId}/statusPOST /admin/api/clients/{clientId}/secret/rotate
- Inbox
GET /admin/api/inboxGET /admin/api/inbox/unread-countPOST /admin/api/inbox/sendPOST /admin/api/inbox/readPOST /admin/api/inbox/read-allPOST /admin/api/inbox/realtime
GET /oauth2/authorizePOST /oauth2/tokenPOST /oauth2/revokePOST /oauth2/introspectGET /openid/jwksGET /openid/.well-known/openid-configurationGET /openid/.well-known/oauth-authorization-serverGET /openid/userinfoPOST /openid/userinfoGET /openid/loginGET /openid/consentPOST /openid/consent
400:参数不合法(例如字段为空、格式错误)401:鉴权失败(Bearer/Cookie/HMAC)409:资源冲突(如用户名或客户端重复)
错误响应统一格式:
{"error":"..."}