chore(auth): 登录接口豁免全量加密,仅加密密码字段
Some checks failed
Build and Deploy (service.xpcool.com) / build-and-deploy (push) Failing after 37s

- APICrypto plainRoutes 增 /system/auth/login(登录请求不整体加密)
- ResolveLogin 密码字段密文优先解密,兼容全量模式
This commit is contained in:
夏犀麟 2026-08-28 00:28:58 +08:00
parent ebc507ee6c
commit 70cb86176e
3 changed files with 18 additions and 12 deletions

View File

@ -1,6 +1,8 @@
# service.xpcool.com 变更记录
> 倒序最新在上格式YYYY-MM-DD | 类型 | 摘要
2026-08-27 | CHG | 登录接口从全量加密豁免永远只加密密码字段APICrypto plainRoutes /system/auth/login登录请求不整体加密handler 直接收 {username, encryptedKey, encryptedData}登录响应因无会话密钥保持明文ResolveLogin 分支顺序调整密码字段密文优先解密其次 fullBody 明文开发 allowPlain全量模式验证 5 登录明文响应/info 密文/解密成功/未加密拒绝/登出+ dev 回归 15 项全过
2026-08-27 | FEAT | 全量请求/响应加密生产 encrypt.fullBody=true / env ENCRYPT_FULL_BODYcrypto.Service fullBody 开关 + DecryptRequestRSA AES 会话密钥 + AES-GCM body返回会话密钥+ EncryptResponse用同一会话密钥 AES-GCM 加密响应回传请求结束即销毁新增 middleware/APICrypto请求整体解密io.ReadAll 直接读 r.Request.Body 勿用 GetBody否则缓存密文致 handler Parse 读到密文+ 响应加密 bufferHandlerResponse 外层Recover 内层500 也加密public-key 明文豁免未加密业务请求一律拒绝admin 组中间件链改 CORSAPICryptoRecoverHandlerResponseResolveLogin/resolvePassword fullBody 分支传输层已整体解密直接信任明文 username/passwordinjectEnv 支持 ENCRYPT_FULL_BODY/ENCRYPT_ALLOW_PLAIN直接跑二进制恒加载 config.yamlGF_GCFG_ENV gf run 必须 env 注入GF r.GetBody() 缓存密文到 bodyContenthandler Parse 走缓存 中间件必须 io.ReadAll(r.Request.Body)dev仅密码加密E2E 15 prod全量加密E2E 9 项全过
2026-08-27 | FEAT | 登录密码RSA + AES-GCM混合加密传输对称+非对称结合+ 创建/重置密码加密字段新建 internal/library/crypto密钥优先级 配置 encrypt.privateKey(PEM) > data/crypto/rsa_private.pem > 自动生成落盘data/ gitignore公钥输出 SPKIx509.MarshalPKIXPublicKey前端 WebCrypto importKey('spki')PKCS#1 ASN.1 wrong tagDecryptLogin 解密 {username,password,ts}ts 5 分钟窗口防重放DecryptField 解密 {value,ts}创建/重置密码复用进程级 SetDefault/Get 单例公开接口 POST /system/auth/public-key 返回 publicKeyLoginReq encryptedKey+encryptedData明文字段仅 encrypt.allowPlain=true 时可用config.yaml/prod 默认 falsedev truecontroller.ResolveLogin 统一解析凭据AdminCreate/AdminResetPwd 同样支持加密 passwordresolvePassword 复用 DecryptField密文结构encryptedData=base64(nonce(12B)||ciphertext||tag)encryptedKey=base64(RSA-OAEP(SHA-256) 加密 AES-256 密钥)端到端 Node 模拟 WebCrypto 15 项全过公钥/加密登录/错密码30002/篡改密文/过期载荷/受保护接口/加密创建/加密重置/旧密码失效/登出撤销/登出后刷新拒绝数据库密码本就是 bcrypt 哈希保存bcrypt.GenerateFromPassword存储安全已达标

View File

@ -26,8 +26,11 @@ import (
// aesKeyCtxVar 请求上下文中 AES 会话密钥base64 字符串)的键名。
const aesKeyCtxVar = "__crypto_aes_key"
// plainRoutes 全量加密豁免路由path 片段匹配),此类端点明文收发。
var plainRoutes = []string{"/public-key"}
// plainRoutes 全量加密豁免路由path 片段匹配):
// - /public-key必须先明文拿到公钥才能发起加密请求
// - /system/auth/login登录永远只加密密码字段username 明文 + 密码字段混合加密),
// 便于客户端兼容与登录联调,登录响应因此保持明文(不含会话密钥)。
var plainRoutes = []string{"/public-key", "/system/auth/login"}
// isPlainRoute 判断当前请求是否属于明文豁免端点。
func isPlainRoute(r *ghttp.Request) bool {

View File

@ -70,17 +70,12 @@ func (s *adminAuth) Login(ctx context.Context, in dto.AdminLoginInput) (*dto.Tok
return s.issue(ctx, a.Id, "")
}
// ResolveLogin 解析登录凭据,返回明文用户名与密码:
// - 全量加密模式fullBody传输层 APICrypto 已整体解密,前端提交明文 username/password
// - 仅密码加密模式携带密文encryptedKey + encryptedData时走 RSA+AES 混合解密,
// 否则仅当服务端 encrypt.allowPlain=true开发联调时接受明文生产一律拒绝。
// ResolveLogin 解析登录凭据,返回明文用户名与密码。
// 登录永远只加密密码字段(全量加密模式下同样豁免登录的整体加密):
// - 携带密文encryptedKey + encryptedData密码字段 RSA+AES 混合加密)时优先解密;
// - 否则全量加密模式fullBody传输层已整体解密信任前端提交的明文
// - 开发环境 allowPlain=true 时接受明文,生产一律拒绝。
func (s *adminAuth) ResolveLogin(ctx context.Context, encryptedKey, encryptedData, username, password string) (string, string, error) {
if s.crypto.FullBody() {
if username == "" || password == "" {
return "", "", response.Error(consts.CodeInvalidParam, "username or password required")
}
return username, password, nil
}
if encryptedKey != "" && encryptedData != "" {
payload, err := s.crypto.DecryptLogin(ctx, encryptedKey, encryptedData)
if err != nil {
@ -88,6 +83,12 @@ func (s *adminAuth) ResolveLogin(ctx context.Context, encryptedKey, encryptedDat
}
return payload.Username, payload.Password, nil
}
if s.crypto.FullBody() {
if username == "" || password == "" {
return "", "", response.Error(consts.CodeInvalidParam, "username or password required")
}
return username, password, nil
}
if s.crypto.AllowPlain() {
return username, password, nil
}