From c09b51ba02a7af9df18c8dff9c54408e56ed9afb Mon Sep 17 00:00:00 2001 From: laoboli <1293528695@qq.com> Date: Thu, 20 Mar 2025 16:40:39 +0800 Subject: [PATCH] feat: auth. --- go.mod | 3 +- go.sum | 6 ++-- middleware/auth.go | 84 ++++++++++++++++++++++++++++++++++++++++++++++ routes/routes.go | 23 ++++++++++++- 4 files changed, 112 insertions(+), 4 deletions(-) create mode 100644 middleware/auth.go diff --git a/go.mod b/go.mod index f59f430..5679dac 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,8 @@ go 1.23.3 require ( github.com/gin-gonic/gin v1.10.0 - github.com/lib/pq v1.10.9 + github.com/golang-jwt/jwt/v4 v4.5.1 + github.com/golang-jwt/jwt/v5 v5.2.1 github.com/spf13/viper v1.20.0 gorm.io/driver/postgres v1.5.11 gorm.io/gorm v1.25.12 diff --git a/go.sum b/go.sum index 1cd3b79..92f4749 100644 --- a/go.sum +++ b/go.sum @@ -31,6 +31,10 @@ github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIx github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= +github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -58,8 +62,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= -github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= diff --git a/middleware/auth.go b/middleware/auth.go new file mode 100644 index 0000000..78a091a --- /dev/null +++ b/middleware/auth.go @@ -0,0 +1,84 @@ +package middleware + +import ( + "fmt" + "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt/v5" + "net/http" + "strings" + "time" +) + +const ( + ApiSecret = "your-super-secret-key" // 预共享密钥 + TokenExp = 1 * time.Hour // Token有效期 +) + +type JWTService struct { + secretKey []byte + expiresIn time.Duration +} + +func NewJWTService(secret string, expiresIn time.Duration) *JWTService { + return &JWTService{ + secretKey: []byte(secret), + expiresIn: expiresIn, + } +} + +// 生成带HMAC签名的Token +func (s *JWTService) GenerateToken() (string, error) { + claims := jwt.MapClaims{ + "exp": time.Now().Add(s.expiresIn).Unix(), + "iat": time.Now().Unix(), + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + return token.SignedString(s.secretKey) +} + +// 验证HMAC签名的Token +func (s *JWTService) ValidateToken(tokenString string) (jwt.Claims, error) { + token, err := jwt.Parse(tokenString, func(t *jwt.Token) (interface{}, error) { + if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, jwt.ErrSignatureInvalid + } + return s.secretKey, nil + }) + + if err != nil { + return nil, err + } + + if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { + return claims, nil + } + + return nil, jwt.ErrTokenInvalidClaims +} + +// 鉴权中间件 +func AuthMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + authHeader := c.GetHeader("Authorization") + if authHeader == "" { + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Authorization header required"}) + return + } + + tokenString := strings.TrimPrefix(authHeader, "Bearer ") + token, err := jwt.Parse(tokenString, func(t *jwt.Token) (interface{}, error) { + if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"]) + } + return []byte(ApiSecret), nil + }) + + if err != nil || !token.Valid { + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid token"}) + return + } + + c.Next() + } +} diff --git a/routes/routes.go b/routes/routes.go index f732cd1..e235785 100644 --- a/routes/routes.go +++ b/routes/routes.go @@ -4,20 +4,41 @@ import ( "github.com/gin-gonic/gin" "hr_receiver/controllers" "hr_receiver/middleware" + "net/http" ) func SetupRouter() *gin.Engine { + jwtService := middleware.NewJWTService(middleware.ApiSecret, middleware.TokenExp) r := gin.Default() r.Use(middleware.GzipMiddleware()) trainingController := controllers.NewTrainingController() v1 := r.Group("/api/v1") { - records := v1.Group("/train-records") + records := v1.Group("/train-records").Use(middleware.AuthMiddleware()) { records.POST("", trainingController.CreateTrainingRecord) // 可扩展其他路由:GET, PUT, DELETE等 } + auth := v1.Group("/auth") + { + auth.GET("/token", func(c *gin.Context) { + + clientSecret := c.GetHeader("X-API-Key") + if clientSecret != middleware.ApiSecret { + c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid secret"}) + return + } + + token, err := jwtService.GenerateToken() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate token"}) + return + } + + c.JSON(http.StatusOK, gin.H{"token": token}) + }) + } } return r }