Files
kenaito-notebook/server/services/auth.go
T

143 lines
3.4 KiB
Go

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
}