| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131 |
- 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 类型解耦,保证所有调用方可以复用")
- }
|