fix: 修复4个安全漏洞 (HIGH-01, HIGH-02, MED-01, MED-02)
- HIGH-01: CheckScope空scope绕过权限检查 * 修复: 空scope现在返回false拒绝访问 - HIGH-02: JWT算法验证不严格 * 修复: 使用token.Method.Alg()严格验证只接受HS256 - MED-01: RequireAnyScope空scope列表逻辑错误 * 修复: 空列表现在返回403拒绝访问 - MED-02: Token状态缓存未命中时默认返回active * 修复: 添加TokenStatusBackend接口,缓存未命中时必须查询后端 影响文件: - supply-api/internal/iam/middleware/scope_auth.go - supply-api/internal/middleware/auth.go - supply-api/cmd/supply-api/main.go (适配新API) 测试覆盖: - 添加4个新的安全测试用例 - 更新1个原有测试以反映正确的安全行为
This commit is contained in:
@@ -320,6 +320,107 @@ func TestTokenCache(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// HIGH-02: JWT算法验证不严格 - 应该拒绝非HS256的算法
|
||||
func TestHIGH02_JWT_RejectNonHS256Algorithm(t *testing.T) {
|
||||
secretKey := "test-secret-key-12345678901234567890"
|
||||
issuer := "test-issuer"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
signingMethod jwt.SigningMethod
|
||||
expectError bool
|
||||
errorContains string
|
||||
}{
|
||||
{
|
||||
name: "HS256 should be accepted",
|
||||
signingMethod: jwt.SigningMethodHS256,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "HS384 should be rejected",
|
||||
signingMethod: jwt.SigningMethodHS384,
|
||||
expectError: true,
|
||||
errorContains: "unexpected signing method",
|
||||
},
|
||||
{
|
||||
name: "HS512 should be rejected",
|
||||
signingMethod: jwt.SigningMethodHS512,
|
||||
expectError: true,
|
||||
errorContains: "unexpected signing method",
|
||||
},
|
||||
{
|
||||
name: "none algorithm should be rejected",
|
||||
signingMethod: jwt.SigningMethodNone,
|
||||
expectError: true,
|
||||
errorContains: "malformed",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
claims := TokenClaims{
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
Issuer: issuer,
|
||||
Subject: "subject:1",
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
},
|
||||
SubjectID: "subject:1",
|
||||
Role: "owner",
|
||||
Scope: []string{"read", "write"},
|
||||
TenantID: 1,
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(tt.signingMethod, claims)
|
||||
tokenString, _ := token.SignedString([]byte(secretKey))
|
||||
|
||||
middleware := &AuthMiddleware{
|
||||
config: AuthConfig{
|
||||
SecretKey: secretKey,
|
||||
Issuer: issuer,
|
||||
},
|
||||
}
|
||||
|
||||
_, err := middleware.verifyToken(tokenString)
|
||||
|
||||
if tt.expectError {
|
||||
if err == nil {
|
||||
t.Errorf("expected error but got nil")
|
||||
} else if tt.errorContains != "" && !strings.Contains(err.Error(), tt.errorContains) {
|
||||
t.Errorf("error = %v, want contains %v", err, tt.errorContains)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// MED-02: checkTokenStatus缓存未命中时应该查询后端而不是默认返回active
|
||||
func TestMED02_TokenCacheMiss_ShouldNotAssumeActive(t *testing.T) {
|
||||
// arrange
|
||||
middleware := &AuthMiddleware{
|
||||
config: AuthConfig{
|
||||
SecretKey: "test-secret-key-12345678901234567890",
|
||||
Issuer: "test-issuer",
|
||||
},
|
||||
tokenCache: NewTokenCache(), // 空的缓存
|
||||
// 没有设置tokenBackend
|
||||
}
|
||||
|
||||
// act - 查询一个不在缓存中的token
|
||||
status, err := middleware.checkTokenStatus("nonexistent-token-id")
|
||||
|
||||
// assert - 缓存未命中且没有后端时应该返回错误(安全修复)
|
||||
// 修复前bug:缓存未命中时默认返回"active"
|
||||
// 修复后:缓存未命中且没有后端时返回错误
|
||||
if err == nil {
|
||||
t.Errorf("MED-02: cache miss without backend should return error, got status='%s'", status)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
func createTestToken(secretKey, issuer, subject, role string, expiresAt time.Time) string {
|
||||
|
||||
Reference in New Issue
Block a user