package middleware import ( "crypto/hmac" "crypto/sha256" "encoding/base64" "encoding/json" "testing" "time" "perms-system-server/internal/consts" "github.com/golang-jwt/jwt/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // --------------------------------------------------------------------------- // 覆盖目标:审计 L-N1 修复 —— ParseWithHMAC 上移到 middleware 层作为唯一入口。 // 本测试直接在 middleware 包内验证: // (1) 正常 HS256 token 放行 // (2) alg=RS256(公钥→HMAC 共享密钥混淆)显式拒绝 // (3) alg=none 拒绝 // (4) 错误 secret 签名拒绝 // (5) 非 Claims 结构的 claims 同样正确解析(保证函数与具体 claims 类型解耦) // --------------------------------------------------------------------------- const ln1Secret = "ln1-centralized-secret" func b64urlLN1(b []byte) string { return base64.RawURLEncoding.EncodeToString(b) } func forgeTokenLN1(t *testing.T, alg string, claims any, signKey string) string { t.Helper() header := map[string]string{"alg": alg, "typ": "JWT"} hBytes, err := json.Marshal(header) require.NoError(t, err) pBytes, err := json.Marshal(claims) require.NoError(t, err) signingInput := b64urlLN1(hBytes) + "." + b64urlLN1(pBytes) mac := hmac.New(sha256.New, []byte(signKey)) mac.Write([]byte(signingInput)) return signingInput + "." + b64urlLN1(mac.Sum(nil)) } func validAccessClaimsLN1() Claims { now := time.Now() return Claims{ TokenType: consts.TokenTypeAccess, UserId: 42, Username: "ln1_u", ProductCode: "ln1_p", MemberType: consts.MemberTypeAdmin, TokenVersion: 0, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(now.Add(1 * time.Hour)), IssuedAt: jwt.NewNumericDate(now), }, } } // TC-1003: L-N1 —— middleware 层 ParseWithHMAC 能正确解析合法 HS256 token。 func TestMiddlewareParseWithHMAC_LN1_HS256Valid(t *testing.T) { signed := jwt.NewWithClaims(jwt.SigningMethodHS256, validAccessClaimsLN1()) tok, err := signed.SignedString([]byte(ln1Secret)) require.NoError(t, err) parsed, err := ParseWithHMAC(tok, ln1Secret, &Claims{}) require.NoError(t, err) require.True(t, parsed.Valid) claims, ok := parsed.Claims.(*Claims) require.True(t, ok) assert.Equal(t, int64(42), claims.UserId) assert.Equal(t, consts.TokenTypeAccess, claims.TokenType) } // TC-1004: L-N1 —— middleware 层 ParseWithHMAC 必须拒绝 alg=RS256 伪造(公钥→HMAC 混淆)。 func TestMiddlewareParseWithHMAC_LN1_RS256HeaderRejected(t *testing.T) { forged := forgeTokenLN1(t, "RS256", validAccessClaimsLN1(), ln1Secret) _, err := ParseWithHMAC(forged, ln1Secret, &Claims{}) require.Error(t, err, "L-N1:必须拒绝 alg=RS256 伪造 token") assert.Contains(t, err.Error(), "unexpected signing method", "L-N1:HMAC 断言失败必须产出可审计错误信息,方便 SOC 定位攻击尝试") } // TC-1005: L-N1 —— middleware 层 ParseWithHMAC 必须拒绝 alg=none。 func TestMiddlewareParseWithHMAC_LN1_AlgNoneRejected(t *testing.T) { header := map[string]string{"alg": "none", "typ": "JWT"} hBytes, _ := json.Marshal(header) pBytes, _ := json.Marshal(validAccessClaimsLN1()) forged := b64urlLN1(hBytes) + "." + b64urlLN1(pBytes) + "." _, err := ParseWithHMAC(forged, ln1Secret, &Claims{}) require.Error(t, err, "L-N1:alg=none 不可通过 HMAC 唯一入口") } // TC-1006: L-N1 —— 错误 secret 签发的合法结构 HS256 token 必须被拒绝。 func TestMiddlewareParseWithHMAC_LN1_WrongSecretRejected(t *testing.T) { signed := jwt.NewWithClaims(jwt.SigningMethodHS256, validAccessClaimsLN1()) tok, err := signed.SignedString([]byte("attacker-guess")) require.NoError(t, err) _, err = ParseWithHMAC(tok, ln1Secret, &Claims{}) require.Error(t, err, "L-N1:签名校验失败必须 fail-close") } // TC-1007: L-N1 —— ParseWithHMAC 可以为任意 jwt.Claims 结构体工作(不绑 Claims 类型), // 保证 gRPC VerifyToken、RefreshToken、HTTP 中间件等所有调用点可以共用该入口。 func TestMiddlewareParseWithHMAC_LN1_ArbitraryClaimsType(t *testing.T) { type customClaims struct { Role string `json:"role"` jwt.RegisteredClaims } c := customClaims{ Role: "admin", RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)), IssuedAt: jwt.NewNumericDate(time.Now()), }, } signed := jwt.NewWithClaims(jwt.SigningMethodHS256, c) tok, err := signed.SignedString([]byte(ln1Secret)) require.NoError(t, err) parsed, err := ParseWithHMAC(tok, ln1Secret, &customClaims{}) require.NoError(t, err) parsedClaims, ok := parsed.Claims.(*customClaims) require.True(t, ok) assert.Equal(t, "admin", parsedClaims.Role, "L-N1:唯一入口必须对任意 claims 类型解耦,保证所有调用方可以复用") }