✅ 新增功能: 1. 用户认证数据库模型 - AuthToken (认证令牌) - LoginAttempt (登录尝试记录) - PasswordReset (密码重置) - Session (用户会话) 2. 认证服务 (AuthService) - 用户登录/注册 - 令牌刷新 - 密码重置 - 会话管理 3. JWT管理器 - 访问令牌生成/验证 - 刷新令牌管理 - 密码重置令牌 - API令牌支持 🔒 安全特性: - bcrypt密码加密 - JWT令牌验证 - 登录尝试记录 - 会话管理 - 令牌撤销机制 📝 技术实现: - 使用GORM进行数据库操作 - JWT v5进行令牌管理 - 完整的错误处理 - 详细的日志记录 作者:小弟 (大哥的AI助手) 分支:feature/user-authentication
249 lines
6.5 KiB
Go
249 lines
6.5 KiB
Go
package jwt
|
|
|
|
import (
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/golang-jwt/jwt/v5"
|
|
)
|
|
|
|
// Claims JWT声明
|
|
type Claims struct {
|
|
UserID uint `json:"user_id"`
|
|
TenantID uint `json:"tenant_id"`
|
|
Role string `json:"role"`
|
|
jwt.RegisteredClaims
|
|
}
|
|
|
|
// JWTManager JWT管理器
|
|
type JWTManager struct {
|
|
secretKey []byte
|
|
}
|
|
|
|
// NewJWTManager 创建JWT管理器
|
|
func NewJWTManager(secretKey string) *JWTManager {
|
|
return &JWTManager{
|
|
secretKey: []byte(secretKey),
|
|
}
|
|
}
|
|
|
|
// GenerateAccessToken 生成访问令牌
|
|
func (m *JWTManager) GenerateAccessToken(userID, tenantID uint, role string) (string, time.Time, error) {
|
|
expiresAt := time.Now().Add(24 * time.Hour) // 24小时有效
|
|
|
|
claims := &Claims{
|
|
UserID: userID,
|
|
TenantID: tenantID,
|
|
Role: role,
|
|
RegisteredClaims: jwt.RegisteredClaims{
|
|
ExpiresAt: jwt.NewNumericDate(expiresAt),
|
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
|
Issuer: "smart-customer-service",
|
|
Subject: "access_token",
|
|
},
|
|
}
|
|
|
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
|
tokenString, err := token.SignedString(m.secretKey)
|
|
if err != nil {
|
|
return "", time.Time{}, err
|
|
}
|
|
|
|
return tokenString, expiresAt, nil
|
|
}
|
|
|
|
// GenerateRefreshToken 生成刷新令牌
|
|
func (m *JWTManager) GenerateRefreshToken(userID, tenantID uint) (string, time.Time, error) {
|
|
expiresAt := time.Now().Add(7 * 24 * time.Hour) // 7天有效
|
|
|
|
claims := &Claims{
|
|
UserID: userID,
|
|
TenantID: tenantID,
|
|
RegisteredClaims: jwt.RegisteredClaims{
|
|
ExpiresAt: jwt.NewNumericDate(expiresAt),
|
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
|
Issuer: "smart-customer-service",
|
|
Subject: "refresh_token",
|
|
},
|
|
}
|
|
|
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
|
tokenString, err := token.SignedString(m.secretKey)
|
|
if err != nil {
|
|
return "", time.Time{}, err
|
|
}
|
|
|
|
return tokenString, expiresAt, nil
|
|
}
|
|
|
|
// ValidateToken 验证令牌
|
|
func (m *JWTManager) ValidateToken(tokenString string) (*Claims, error) {
|
|
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
|
|
// 验证签名方法
|
|
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
|
return nil, errors.New("无效的签名方法")
|
|
}
|
|
return m.secretKey, nil
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
|
|
return claims, nil
|
|
}
|
|
|
|
return nil, errors.New("无效的令牌")
|
|
}
|
|
|
|
// ParseToken 解析令牌(不验证过期)
|
|
func (m *JWTManager) ParseToken(tokenString string) (*Claims, error) {
|
|
parser := jwt.NewParser(jwt.WithoutClaimsValidation())
|
|
token, _, err := parser.ParseUnverified(tokenString, &Claims{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if claims, ok := token.Claims.(*Claims); ok {
|
|
return claims, nil
|
|
}
|
|
|
|
return nil, errors.New("无法解析令牌声明")
|
|
}
|
|
|
|
// GetTokenExpiration 获取令牌过期时间
|
|
func (m *JWTManager) GetTokenExpiration(tokenString string) (time.Time, error) {
|
|
claims, err := m.ParseToken(tokenString)
|
|
if err != nil {
|
|
return time.Time{}, err
|
|
}
|
|
|
|
return claims.ExpiresAt.Time, nil
|
|
}
|
|
|
|
// IsTokenExpired 检查令牌是否过期
|
|
func (m *JWTManager) IsTokenExpired(tokenString string) (bool, error) {
|
|
expiresAt, err := m.GetTokenExpiration(tokenString)
|
|
if err != nil {
|
|
return true, err
|
|
}
|
|
|
|
return time.Now().After(expiresAt), nil
|
|
}
|
|
|
|
// GeneratePasswordResetToken 生成密码重置令牌
|
|
func (m *JWTManager) GeneratePasswordResetToken(userID, tenantID uint) (string, time.Time, error) {
|
|
expiresAt := time.Now().Add(1 * time.Hour) // 1小时有效
|
|
|
|
claims := &Claims{
|
|
UserID: userID,
|
|
TenantID: tenantID,
|
|
RegisteredClaims: jwt.RegisteredClaims{
|
|
ExpiresAt: jwt.NewNumericDate(expiresAt),
|
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
|
Issuer: "smart-customer-service",
|
|
Subject: "password_reset",
|
|
},
|
|
}
|
|
|
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
|
tokenString, err := token.SignedString(m.secretKey)
|
|
if err != nil {
|
|
return "", time.Time{}, err
|
|
}
|
|
|
|
return tokenString, expiresAt, nil
|
|
}
|
|
|
|
// GenerateEmailVerificationToken 生成邮箱验证令牌
|
|
func (m *JWTManager) GenerateEmailVerificationToken(userID, tenantID uint) (string, time.Time, error) {
|
|
expiresAt := time.Now().Add(24 * time.Hour) // 24小时有效
|
|
|
|
claims := &Claims{
|
|
UserID: userID,
|
|
TenantID: tenantID,
|
|
RegisteredClaims: jwt.RegisteredClaims{
|
|
ExpiresAt: jwt.NewNumericDate(expiresAt),
|
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
|
Issuer: "smart-customer-service",
|
|
Subject: "email_verification",
|
|
},
|
|
}
|
|
|
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
|
tokenString, err := token.SignedString(m.secretKey)
|
|
if err != nil {
|
|
return "", time.Time{}, err
|
|
}
|
|
|
|
return tokenString, expiresAt, nil
|
|
}
|
|
|
|
// GenerateAPIToken 生成API令牌
|
|
func (m *JWTManager) GenerateAPIToken(tenantID uint, permissions []string) (string, time.Time, error) {
|
|
expiresAt := time.Now().Add(30 * 24 * time.Hour) // 30天有效
|
|
|
|
claims := &Claims{
|
|
TenantID: tenantID,
|
|
Role: "api",
|
|
RegisteredClaims: jwt.RegisteredClaims{
|
|
ExpiresAt: jwt.NewNumericDate(expiresAt),
|
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
|
Issuer: "smart-customer-service",
|
|
Subject: "api_token",
|
|
},
|
|
}
|
|
|
|
// 添加自定义声明
|
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
|
|
|
// 添加权限声明
|
|
token.Claims.(jwt.MapClaims)["permissions"] = permissions
|
|
|
|
tokenString, err := token.SignedString(m.secretKey)
|
|
if err != nil {
|
|
return "", time.Time{}, err
|
|
}
|
|
|
|
return tokenString, expiresAt, nil
|
|
}
|
|
|
|
// GetTokenClaims 获取令牌声明(安全版本)
|
|
func (m *JWTManager) GetTokenClaims(tokenString string) (map[string]interface{}, error) {
|
|
claims, err := m.ValidateToken(tokenString)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return map[string]interface{}{
|
|
"user_id": claims.UserID,
|
|
"tenant_id": claims.TenantID,
|
|
"role": claims.Role,
|
|
"exp": claims.ExpiresAt.Time.Unix(),
|
|
"iat": claims.IssuedAt.Time.Unix(),
|
|
"iss": claims.Issuer,
|
|
"sub": claims.Subject,
|
|
}, nil
|
|
}
|
|
|
|
// RenewToken 续期令牌
|
|
func (m *JWTManager) RenewToken(tokenString string) (string, time.Time, error) {
|
|
claims, err := m.ValidateToken(tokenString)
|
|
if err != nil {
|
|
return "", time.Time{}, err
|
|
}
|
|
|
|
// 根据令牌类型续期
|
|
switch claims.Subject {
|
|
case "access_token":
|
|
return m.GenerateAccessToken(claims.UserID, claims.TenantID, claims.Role)
|
|
case "refresh_token":
|
|
return m.GenerateRefreshToken(claims.UserID, claims.TenantID)
|
|
case "api_token":
|
|
// API令牌不自动续期
|
|
return "", time.Time{}, errors.New("API令牌不支持自动续期")
|
|
default:
|
|
return "", time.Time{}, errors.New("未知的令牌类型")
|
|
}
|
|
} |