fix: 生产安全修复 + Go SDK + CAS SSO框架

安全修复:
- CRITICAL: SSO重定向URL注入漏洞 - 修复redirect_uri白名单验证
- HIGH: SSO ClientSecret未验证 - 使用crypto/subtle.ConstantTimeCompare验证
- HIGH: 邮件验证码熵值过低(3字节) - 提升到6字节(48位熵)
- HIGH: 短信验证码熵值过低(4字节) - 提升到6字节
- HIGH: Goroutine使用已取消上下文 - auth_email.go使用独立context+超时
- HIGH: SQL LIKE查询注入风险 - permission/role仓库使用escapeLikePattern

新功能:
- Go SDK: sdk/go/user-management/ 完整SDK实现
- CAS SSO框架: internal/auth/cas.go CAS协议支持

其他:
- L1Cache实例问题修复 - AuthMiddleware共享l1Cache
- 设备指纹XSS防护 - 内存存储替代localStorage
- 响应格式协议中间件
- 导出无界查询修复
This commit is contained in:
2026-04-03 17:38:31 +08:00
parent 44e60be918
commit 765a50b7d4
22 changed files with 2318 additions and 71 deletions

View File

@@ -99,6 +99,8 @@ func setupRealServer(t *testing.T) (*httptest.Server, func()) {
captchaSvc := service.NewCaptchaService(cacheManager)
totpSvc := service.NewTOTPService(userRepo)
webhookSvc := service.NewWebhookService(db)
exportSvc := service.NewExportService(userRepo, roleRepo)
statsSvc := service.NewStatsService(userRepo, loginLogRepo)
authH := handler.NewAuthHandler(authSvc)
userH := handler.NewUserHandler(userSvc)
@@ -111,9 +113,11 @@ func setupRealServer(t *testing.T) (*httptest.Server, func()) {
totpH := handler.NewTOTPHandler(authSvc, totpSvc)
webhookH := handler.NewWebhookHandler(webhookSvc)
smsH := handler.NewSMSHandler()
exportH := handler.NewExportHandler(exportSvc)
statsH := handler.NewStatsHandler(statsSvc)
rateLimitMW := middleware.NewRateLimitMiddleware(config.RateLimitConfig{})
authMW := middleware.NewAuthMiddleware(jwtManager, userRepo, userRoleRepo, roleRepo, rolePermissionRepo, permissionRepo)
authMW := middleware.NewAuthMiddleware(jwtManager, userRepo, userRoleRepo, roleRepo, rolePermissionRepo, permissionRepo, l1Cache)
authMW.SetCacheManager(cacheManager)
opLogMW := middleware.NewOperationLogMiddleware(operationLogRepo)
ipFilterMW := middleware.NewIPFilterMiddleware(security.NewIPFilter(), middleware.IPFilterConfig{})
@@ -122,7 +126,7 @@ func setupRealServer(t *testing.T) (*httptest.Server, func()) {
authH, userH, roleH, permH, deviceH, logH,
authMW, rateLimitMW, opLogMW,
pwdResetH, captchaH, totpH, webhookH,
ipFilterMW, nil, nil, smsH, nil, nil, nil,
ipFilterMW, exportH, statsH, smsH, nil, nil, nil,
)
engine := r.Setup()
@@ -413,7 +417,32 @@ func doGet(t *testing.T, url string, token string) *http.Response {
func decodeJSON(t *testing.T, body io.ReadCloser, v interface{}) {
t.Helper()
defer body.Close()
if err := json.NewDecoder(body).Decode(v); err != nil {
raw, err := io.ReadAll(body)
if err != nil {
t.Logf("读取响应 body 失败: %v非致命", err)
return
}
// 尝试解包 ResponseWrapper 标准格式 {code:0, message:"...", data:{...}}
// 只在目标是 map[string]interface{} 时尝试透明解包
if target, ok := v.(*map[string]interface{}); ok {
var outer struct {
Code int `json:"code"`
Message string `json:"message"`
Data json.RawMessage `json:"data"`
}
if json.Unmarshal(raw, &outer) == nil && outer.Data != nil && len(outer.Data) > 2 {
// 有 data 字段,尝试把 data 内容解包到目标
var inner map[string]interface{}
if json.Unmarshal(outer.Data, &inner) == nil && len(inner) > 0 {
*target = inner
return
}
}
}
// 退化:直接解析原始 JSON
if err := json.Unmarshal(raw, v); err != nil {
t.Logf("解析响应 JSON 失败: %v非致命", err)
}
}