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:
410
PROJECT_QUALITY_STANDARDS.md
Normal file
410
PROJECT_QUALITY_STANDARDS.md
Normal file
@@ -0,0 +1,410 @@
|
||||
# 项目质量规范 (Production Quality Standards)
|
||||
|
||||
**版本**: 1.0
|
||||
**更新日期**: 2026-04-03
|
||||
**适用范围**: D:\project (Go + React/TypeScript)
|
||||
|
||||
---
|
||||
|
||||
## 一、安全规范 (Security)
|
||||
|
||||
### 1.1 加密与随机数
|
||||
|
||||
```go
|
||||
// ✅ 正确:随机数生成失败时返回错误
|
||||
func generateSecureToken(length int) (string, error) {
|
||||
bytes := make([]byte, length)
|
||||
if _, err := rand.Read(bytes); err != nil {
|
||||
return "", fmt.Errorf("failed to generate secure token: %w", err)
|
||||
}
|
||||
return base64.URLEncoding.EncodeToString(bytes)[:length], nil
|
||||
}
|
||||
|
||||
// ❌ 禁止:使用不安全 fallback
|
||||
func generateSecureToken(length int) string {
|
||||
// ...
|
||||
if _, err := rand.Read(bytes); err != nil {
|
||||
// 禁止使用时间戳或 math/rand 作为 fallback
|
||||
for i := range bytes {
|
||||
bytes[i] = byte(time.Now().UnixNano() % 256) // 不安全!
|
||||
}
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### 1.2 敏感数据存储
|
||||
|
||||
```typescript
|
||||
// ✅ 正确:敏感数据使用内存存储
|
||||
let deviceFingerprintCache: DeviceFingerprint | null = null
|
||||
export function getDeviceFingerprint(): DeviceFingerprint {
|
||||
if (cachedFingerprint) return cachedFingerprint
|
||||
cachedFingerprint = buildFingerprint()
|
||||
return cachedFingerprint
|
||||
}
|
||||
|
||||
// ❌ 禁止:敏感数据存入 localStorage/sessionStorage
|
||||
localStorage.setItem('device_id', deviceId) // XSS 可读取
|
||||
localStorage.setItem('token', token) // XSS 可读取
|
||||
```
|
||||
|
||||
### 1.3 认证与授权
|
||||
|
||||
```go
|
||||
// ✅ 正确:所有受保护路由使用中间件
|
||||
adminRoutes.Use(AuthMiddleware.Required())
|
||||
adminRoutes.Use(AdminOnly())
|
||||
|
||||
// ❌ 禁止:硬编码权限检查
|
||||
if user.Role != "admin" {
|
||||
c.JSON(403, "forbidden") // 分散的权限检查
|
||||
}
|
||||
```
|
||||
|
||||
### 1.4 SQL 注入防护
|
||||
|
||||
```go
|
||||
// ✅ 正确:使用参数化查询
|
||||
db.Where("user_id = ?", userID)
|
||||
db.Where("name LIKE ?", "%"+EscapeLikeWildcard(name)+"%")
|
||||
|
||||
// ❌ 禁止:字符串拼接 SQL
|
||||
db.Where("user_id = " + userID) // SQL 注入风险
|
||||
```
|
||||
|
||||
### 1.5 错误信息泄露
|
||||
|
||||
```go
|
||||
// ✅ 正确:分类错误,不返回原始错误
|
||||
response.Error(c, http.StatusInternalServerError, "服务器内部错误")
|
||||
|
||||
// ❌ 禁止:返回原始错误信息给客户端
|
||||
c.JSON(500, gin.H{"error": err.Error()}) // 可能泄露内部信息
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、并发与性能 (Concurrency & Performance)
|
||||
|
||||
### 2.1 Goroutine 管理
|
||||
|
||||
```go
|
||||
// ✅ 正确:使用 context 控制生命周期
|
||||
go func() {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
cleanup()
|
||||
}
|
||||
}()
|
||||
|
||||
// ❌ 禁止:fire-and-forget goroutine
|
||||
go publishEvent(ctx, event, data) // 无限制的 goroutine
|
||||
```
|
||||
|
||||
### 2.2 Map 并发访问
|
||||
|
||||
```go
|
||||
// ✅ 正确:使用互斥锁保护共享 map
|
||||
type SSOManager struct {
|
||||
mu sync.RWMutex
|
||||
sessions map[string]*SSOSession
|
||||
}
|
||||
func (m *SSOManager) Get(key string) *SSOSession {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
return m.sessions[key]
|
||||
}
|
||||
|
||||
// ❌ 禁止:map 并发读写
|
||||
sessions[key] = session // concurrent map write
|
||||
```
|
||||
|
||||
### 2.3 数据库查询
|
||||
|
||||
```go
|
||||
// ✅ 正确:使用 JOIN 替代 N+1 查询
|
||||
func GetUserRolesAndPermissions(ctx, userID) ([]*Role, []*Permission, error) {
|
||||
// 单次 JOIN 查询
|
||||
rows := db.Raw(`SELECT ... FROM user_roles ur
|
||||
JOIN roles r ON ur.role_id = r.id
|
||||
LEFT JOIN role_permissions rp ON r.id = rp.role_id
|
||||
LEFT JOIN permissions p ON rp.permission_id = p.id
|
||||
WHERE ur.user_id = ?`, userID)
|
||||
}
|
||||
|
||||
// ❌ 禁止:循环内单独查询(N+1)
|
||||
for _, roleID := range roleIDs {
|
||||
ancestors := repo.GetAncestorIDs(ctx, roleID) // 每 role 执行一次查询
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4 导出与批处理
|
||||
|
||||
```go
|
||||
// ✅ 正确:分批处理 + 最大限制
|
||||
const MaxExportRecords = 100000
|
||||
const BatchSize = 5000
|
||||
|
||||
for {
|
||||
batch, hasMore, err := repo.ListBatch(ctx, cursor, BatchSize)
|
||||
if total >= MaxExportRecords {
|
||||
break // 防止 OOM
|
||||
}
|
||||
// 处理 batch...
|
||||
}
|
||||
|
||||
// ❌ 禁止:无限制加载全表到内存
|
||||
allRecords := repo.ListAll(ctx) // 百万级记录 OOM
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、API 设计规范 (API Design)
|
||||
|
||||
### 3.1 响应格式
|
||||
|
||||
```go
|
||||
// ✅ 正确:统一包装响应
|
||||
type APIResponse struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// 成功响应
|
||||
response.Success(c, data) // {code: 0, message: "success", data: {...}}
|
||||
response.Paginated(c, items, total, page, pageSize)
|
||||
|
||||
// ❌ 禁止:裸 JSON 响应
|
||||
c.JSON(200, gin.H{"users": users}) // 无统一格式
|
||||
```
|
||||
|
||||
### 3.2 错误处理
|
||||
|
||||
```go
|
||||
// ✅ 正确:使用标准错误响应
|
||||
response.BadRequest(c, "无效的请求参数")
|
||||
response.Unauthorized(c, "认证已过期,请重新登录")
|
||||
response.Forbidden(c, "权限不足")
|
||||
response.NotFound(c, "用户不存在")
|
||||
response.InternalError(c, "服务器内部错误")
|
||||
|
||||
// ❌ 禁止:直接返回错误字符串
|
||||
c.JSON(400, gin.H{"error": "bad request"})
|
||||
```
|
||||
|
||||
### 3.3 分页参数
|
||||
|
||||
```
|
||||
// ✅ 统一分页格式
|
||||
GET /users?page=1&page_size=20
|
||||
|
||||
// 响应
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"items": [...],
|
||||
"total": 100,
|
||||
"page": 1,
|
||||
"page_size": 20,
|
||||
"pages": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、代码风格规范 (Code Style)
|
||||
|
||||
### 4.1 错误处理原则
|
||||
|
||||
```go
|
||||
// ✅ 正确:明确处理错误
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
return nil, fmt.Errorf("query failed: %w", err)
|
||||
}
|
||||
|
||||
// ❌ 禁止:忽略错误
|
||||
data, _ := json.Marshal(v) // 忽略 marshal 错误
|
||||
```
|
||||
|
||||
### 4.2 Context 使用
|
||||
|
||||
```go
|
||||
// ✅ 正确:使用请求 context 或带超时的 context
|
||||
func HandleRequest(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
// 或
|
||||
ctx, cancel := context.WithTimeout(c.Request.Context(), 5*time.Second)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
// ❌ 禁止:使用 context.Background()
|
||||
go func() {
|
||||
doSomething(context.Background()) // 生命周期不关联
|
||||
}()
|
||||
```
|
||||
|
||||
### 4.3 前端 TypeScript
|
||||
|
||||
```typescript
|
||||
// ✅ 正确:完整的类型定义
|
||||
interface User {
|
||||
id: number
|
||||
username: string
|
||||
email: string
|
||||
}
|
||||
|
||||
// ❌ 禁止:滥用 any
|
||||
function processData(data: any): any {
|
||||
return data // 类型安全丧失
|
||||
}
|
||||
|
||||
// ✅ 正确:useMemo 缓存 expensive 计算
|
||||
const columns = useMemo(() => [
|
||||
{ key: 'name', dataIndex: 'name' },
|
||||
// ...
|
||||
], [dependencies])
|
||||
|
||||
// ❌ 禁止:每次渲染重新创建
|
||||
const columns = [ // 每次渲染创建新数组
|
||||
{ key: 'name', dataIndex: 'name' },
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、测试规范 (Testing)
|
||||
|
||||
### 5.1 单元测试
|
||||
|
||||
```go
|
||||
// ✅ 正确:表驱动测试 + 完整断言
|
||||
func TestLogin(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
req LoginRequest
|
||||
wantErr bool
|
||||
}{
|
||||
{"valid login", LoginRequest{Username: "test", Password: "pass"}, false},
|
||||
{"invalid password", LoginRequest{Username: "test", Password: "wrong"}, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := Login(tt.req)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Login() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 集成测试
|
||||
|
||||
```go
|
||||
// ✅ 正确:使用测试数据库,测试后清理
|
||||
func TestUserCRUD(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
repo := NewUserRepository(db)
|
||||
user, err := repo.Create(ctx, &User{Username: "test"})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create user: %v", err)
|
||||
}
|
||||
|
||||
got, err := repo.GetByID(ctx, user.ID)
|
||||
if err != nil {
|
||||
t.Errorf("GetByID() error = %v", err)
|
||||
}
|
||||
if got.Username != user.Username {
|
||||
t.Errorf("GetByID() = %v, want %v", got.Username, user.Username)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、禁止模式 (Prohibited Patterns)
|
||||
|
||||
### 6.1 安全相关
|
||||
|
||||
| 禁止模式 | 风险 | 正确做法 |
|
||||
|---------|------|---------|
|
||||
| `localStorage.setItem('token', token)` | XSS 读取 | 内存存储或 HttpOnly Cookie |
|
||||
| `crypto/rand` 失败 fallback 到时间戳 | Token 可预测 | 返回错误 |
|
||||
| `c.JSON(500, gin.H{"error": err})` | 内部信息泄露 | 统一错误响应 |
|
||||
| 用户输入拼接 SQL | SQL 注入 | 参数化查询 |
|
||||
| 重定向未验证 URL | Open Redirect | 白名单验证 |
|
||||
|
||||
### 6.2 性能相关
|
||||
|
||||
| 禁止模式 | 风险 | 正确做法 |
|
||||
|---------|------|---------|
|
||||
| `for { repo.Query() }` 循环内查询 | N+1 | JOIN 批量查询 |
|
||||
| `ListAll()` 全量加载 | OOM | 分批 + 最大限制 |
|
||||
| `context.Background()` 在 goroutine | 泄漏 | 带超时的 context |
|
||||
| 共享 map 无锁保护 | panic | `sync.RWMutex` |
|
||||
|
||||
### 6.3 代码质量
|
||||
|
||||
| 禁止模式 | 风险 | 正确做法 |
|
||||
|---------|------|---------|
|
||||
| `data as SomeType` 类型断言 | 运行时 panic | 类型守卫检查 |
|
||||
| 魔法数字 | 可读性差 | 定义常量 |
|
||||
| 重复代码 > 3 处 | 维护性差 | 提取函数/模块 |
|
||||
| 过长函数 > 100 行 | 可读性差 | 拆分为小函数 |
|
||||
|
||||
---
|
||||
|
||||
## 七、审查清单 (Review Checklist)
|
||||
|
||||
### 提交前必须检查
|
||||
|
||||
- [ ] `go vet ./...` 无警告
|
||||
- [ ] `go build ./...` 编译通过
|
||||
- [ ] `npm run build` 前端编译通过
|
||||
- [ ] `npm run lint` 无 error(warning 可接受)
|
||||
- [ ] 无 `TODO: fixme` 或 ` FIXME` 未处理
|
||||
- [ ] 无硬编码密码/密钥/Secret
|
||||
- [ ] 无 `console.log` 生产代码
|
||||
- [ ] 新增 handler 使用 `response.Success()` 而非裸 `c.JSON`
|
||||
- [ ] 敏感数据不写入 localStorage/sessionStorage
|
||||
- [ ] 异步操作有超时控制
|
||||
|
||||
### 安全专项检查
|
||||
|
||||
- [ ] 新增 API 有权限控制
|
||||
- [ ] 用户输入有验证
|
||||
- [ ] SQL 使用参数化查询
|
||||
- [ ] 错误不泄露内部信息
|
||||
- [ ] Token 使用 crypto/rand 生成
|
||||
|
||||
---
|
||||
|
||||
## 八、持续改进
|
||||
|
||||
- 每季度进行一次完整的安全审计
|
||||
- 发现新的反模式及时加入禁止列表
|
||||
- 定期更新依赖版本(安全补丁)
|
||||
- 代码覆盖率目标:核心业务 > 80%
|
||||
|
||||
---
|
||||
|
||||
## 附录:已有安全实践
|
||||
|
||||
- ✅ Argon2id 密码哈希
|
||||
- ✅ JWT JTI 黑名单
|
||||
- ✅ TOTP 两步验证
|
||||
- ✅ CSRF Token 保护
|
||||
- ✅ XSS window guard
|
||||
- ✅ SSRF URL 验证
|
||||
- ✅ 参数化查询防注入
|
||||
Reference in New Issue
Block a user