package services import ( "crypto/hmac" "crypto/sha256" "encoding/base64" "encoding/json" "errors" "fmt" "strings" "time" "golang.org/x/crypto/bcrypt" ) var ( ErrInvalidCredentials = errors.New("邮箱或密码错误") ErrUserExists = errors.New("邮箱已被注册") ErrInvalidToken = errors.New("无效的 token") ErrTokenExpired = errors.New("token 已过期") ) // JWT 密钥,生产环境应从环境变量或配置文件读取 var jwtSecret = []byte("kenaito-notebook-jwt-secret-2026") type jwtHeader struct { Alg string `json:"alg"` Typ string `json:"typ"` } type jwtPayload struct { UserID int64 `json:"userId"` Email string `json:"email"` Exp int64 `json:"exp"` Iat int64 `json:"iat"` } type AuthService struct{} func NewAuthService() *AuthService { return &AuthService{} } // HashPassword 使用 bcrypt 哈希密码 func (s *AuthService) HashPassword(password string) (string, error) { bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) return string(bytes), err } // CheckPassword 验证密码 func (s *AuthService) CheckPassword(password, hash string) bool { err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) return err == nil } // GenerateToken 生成 JWT token func (s *AuthService) GenerateToken(userID int64, email string) (string, error) { now := time.Now() payload := jwtPayload{ UserID: userID, Email: email, Exp: now.Add(7 * 24 * time.Hour).Unix(), // 7 天过期 Iat: now.Unix(), } // Header header := jwtHeader{Alg: "HS256", Typ: "JWT"} headerJSON, err := json.Marshal(header) if err != nil { return "", err } headerB64 := base64.RawURLEncoding.EncodeToString(headerJSON) // Payload payloadJSON, err := json.Marshal(payload) if err != nil { return "", err } payloadB64 := base64.RawURLEncoding.EncodeToString(payloadJSON) // 签名 signingInput := headerB64 + "." + payloadB64 mac := hmac.New(sha256.New, jwtSecret) mac.Write([]byte(signingInput)) signature := base64.RawURLEncoding.EncodeToString(mac.Sum(nil)) return signingInput + "." + signature, nil } // ValidateToken 验证 JWT token,返回 payload func (s *AuthService) ValidateToken(tokenStr string) (*jwtPayload, error) { parts := strings.Split(tokenStr, ".") if len(parts) != 3 { return nil, ErrInvalidToken } signingInput := parts[0] + "." + parts[1] // 验证签名 mac := hmac.New(sha256.New, jwtSecret) mac.Write([]byte(signingInput)) expectedSig := base64.RawURLEncoding.EncodeToString(mac.Sum(nil)) if !hmac.Equal([]byte(parts[2]), []byte(expectedSig)) { return nil, ErrInvalidToken } // 解析 payload payloadJSON, err := base64.RawURLEncoding.DecodeString(parts[1]) if err != nil { return nil, ErrInvalidToken } var payload jwtPayload if err := json.Unmarshal(payloadJSON, &payload); err != nil { return nil, ErrInvalidToken } // 检查过期 if time.Now().Unix() > payload.Exp { return nil, ErrTokenExpired } return &payload, nil } // ExtractTokenID 从 token 字符串中提取用户 ID(不验证签名,仅用于快速检查) func ExtractTokenID(token string) (int64, error) { parts := strings.Split(token, ".") if len(parts) != 3 { return 0, fmt.Errorf("invalid token format") } payloadJSON, err := base64.RawURLEncoding.DecodeString(parts[1]) if err != nil { return 0, err } var payload jwtPayload if err := json.Unmarshal(payloadJSON, &payload); err != nil { return 0, err } return payload.UserID, nil }