// Package auth 提供无状态鉴权能力:JWT 签发/校验 + 密码哈希。 package auth import ( "errors" "os" "time" "github.com/golang-jwt/jwt/v5" "golang.org/x/crypto/bcrypt" ) // TokenTTL 是访问令牌有效期。 const TokenTTL = 24 * time.Hour // secret 是 JWT 签名密钥。生产经环境变量 JWT_SECRET 注入;缺省仅供开发(务必覆盖)。 var secret = []byte(envOr("JWT_SECRET", "sundynix-dev-secret-change-me")) // ErrInvalidToken 表示令牌无效/过期/签名不符。 var ErrInvalidToken = errors.New("invalid token") // Issue 为某用户签发 JWT(subject = userID)。 func Issue(userID string) (string, error) { return issue(userID, TokenTTL) } func issue(userID string, ttl time.Duration) (string, error) { now := time.Now() claims := jwt.RegisteredClaims{ Subject: userID, IssuedAt: jwt.NewNumericDate(now), ExpiresAt: jwt.NewNumericDate(now.Add(ttl)), } return jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(secret) } // Parse 校验 JWT 并返回 userID(subject)。无效/过期/签名不符返回 ErrInvalidToken。 func Parse(token string) (string, error) { t, err := jwt.ParseWithClaims(token, &jwt.RegisteredClaims{}, func(t *jwt.Token) (any, error) { if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { // 仅接受 HMAC,防 alg 混淆攻击 return nil, ErrInvalidToken } return secret, nil }) if err != nil || !t.Valid { return "", ErrInvalidToken } c, ok := t.Claims.(*jwt.RegisteredClaims) if !ok || c.Subject == "" { return "", ErrInvalidToken } return c.Subject, nil } // HashPassword 用 bcrypt 哈希明文密码。 func HashPassword(pw string) (string, error) { b, err := bcrypt.GenerateFromPassword([]byte(pw), bcrypt.DefaultCost) return string(b), err } // CheckPassword 校验明文与 bcrypt 哈希是否匹配。 func CheckPassword(hash, pw string) bool { return bcrypt.CompareHashAndPassword([]byte(hash), []byte(pw)) == nil } func envOr(k, def string) string { if v := os.Getenv(k); v != "" { return v } return def }