From 9f72c30e8bbf50e50b0879e2e1eddad91e6f65d2 Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Fri, 12 Sep 2025 12:25:53 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E5=8F=96token=E9=80=BB=E8=BE=91,=20?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0token=E5=8D=95=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/utils/token/token.go | 60 +++++++++++++++++++ internal/utils/token/token_test.go | 94 ++++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+) create mode 100644 internal/utils/token/token.go create mode 100644 internal/utils/token/token_test.go diff --git a/internal/utils/token/token.go b/internal/utils/token/token.go new file mode 100644 index 0000000..b2bbb12 --- /dev/null +++ b/internal/utils/token/token.go @@ -0,0 +1,60 @@ +package token + +import ( + "time" + + "github.com/golang-jwt/jwt/v5" +) + +// Claims 定义了 JWT 的声明结构 +type Claims struct { + UserID uint `json:"user_id"` + jwt.RegisteredClaims +} + +// TokenService 定义了 token 操作的接口 +type TokenService interface { + GenerateToken(userID uint) (string, error) + ParseToken(tokenString string) (*Claims, error) +} + +// tokenService 是 TokenService 接口的实现 +type tokenService struct { + secret []byte +} + +// NewTokenService 创建并返回一个新的 TokenService 实例 +func NewTokenService(secret []byte) TokenService { + return &tokenService{secret: secret} +} + +// GenerateToken 生成一个新的 JWT token +func (s *tokenService) GenerateToken(userID uint) (string, error) { + nowTime := time.Now() + expireTime := nowTime.Add(24 * time.Hour) // Token 有效期为 24 小时 + + claims := Claims{ + UserID: userID, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(expireTime), + Issuer: "pig-farm-controller", + }, + } + + tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + token, err := tokenClaims.SignedString(s.secret) + return token, err +} + +// ParseToken 解析并验证 JWT token +func (s *tokenService) ParseToken(tokenString string) (*Claims, error) { + token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) { + return s.secret, nil + }) + + if claims, ok := token.Claims.(*Claims); ok && token.Valid { + return claims, nil + } + + return nil, err +} diff --git a/internal/utils/token/token_test.go b/internal/utils/token/token_test.go new file mode 100644 index 0000000..65a8f3c --- /dev/null +++ b/internal/utils/token/token_test.go @@ -0,0 +1,94 @@ +package token + +import ( + "testing" + "time" + + "github.com/golang-jwt/jwt/v5" +) + +func TestGenerateToken(t *testing.T) { + // 使用一个测试密钥初始化 TokenService + testSecret := []byte("test_secret_key") + service := NewTokenService(testSecret) + + userID := uint(123) + tokenString, err := service.GenerateToken(userID) + + if err != nil { + t.Fatalf("GenerateToken failed: %v", err) + } + + if tokenString == "" { + t.Fatal("Generated token string is empty") + } + + // 解析 token 以确保其有效性及声明 + claims, err := service.ParseToken(tokenString) + if err != nil { + t.Fatalf("ParseToken failed after generation: %v", err) + } + + if claims.UserID != userID { + t.Errorf("Expected UserID %d, got %d", userID, claims.UserID) + } + + // 检查 token 是否未过期 (在合理范围内) + if claims.ExpiresAt == nil || claims.ExpiresAt.Time.Before(time.Now().Add(-time.Minute)) { + t.Errorf("Token expiration time is invalid or already expired") + } + + if claims.Issuer != "pig-farm-controller" { + t.Errorf("Expected Issuer \"pig-farm-controller\", got \"%s\"", claims.Issuer) + } +} + +func TestParseToken(t *testing.T) { + // 使用一个测试密钥初始化 TokenService + testSecret := []byte("test_secret_key") + service := NewTokenService(testSecret) + + userID := uint(456) + + // 生成一个有效的 token 用于解析测试 + validToken, err := service.GenerateToken(userID) + if err != nil { + t.Fatalf("Failed to generate valid token for parsing test: %v", err) + } + + // 测试用例 1: 有效 token + claims, err := service.ParseToken(validToken) + if err != nil { + t.Errorf("ParseToken failed for valid token: %v", err) + } + + if claims.UserID != userID { + t.Errorf("Expected UserID %d, got %d for valid token", userID, claims.UserID) + } + + // 测试用例 2: 无效 token (例如, 错误的签名) + invalidToken := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjMsImV4cCI6MTY3ODkwNTYwMCwiaXNzIjoicGlnLWZhcm0tY29udHJvbGxlciJ9.invalid_signature_here" + _, err = service.ParseToken(invalidToken) + if err == nil { + t.Error("ParseToken unexpectedly succeeded for invalid token") + } + + // 测试用例 3: 过期 token (创建一个过期时间在过去的 token) + expiredClaims := Claims{ + UserID: userID, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(time.Now().Add(-time.Hour)), // 1 小时前 + Issuer: "pig-farm-controller", + }, + } + expiredTokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, expiredClaims) + expiredTokenString, err := expiredTokenClaims.SignedString(testSecret) + if err != nil { + t.Fatalf("Failed to generate expired token: %v", err) + } + + _, err = service.ParseToken(expiredTokenString) + if err == nil { + t.Error("ParseToken unexpectedly succeeded for expired token") + } +}