Files
NetworkAuth/services/refresh_token.go

122 lines
3.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package services
import (
"NetworkAuth/database"
"NetworkAuth/models"
"errors"
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
// RefreshTokenService 提供 refreshToken 的持久化、轮换、撤销等业务能力
type RefreshTokenService struct{}
var refreshTokenService = &RefreshTokenService{}
// GetRefreshTokenService 单例获取
func GetRefreshTokenService() *RefreshTokenService {
return refreshTokenService
}
// NewJTI 生成新的 jti
func (s *RefreshTokenService) NewJTI() string {
return uuid.New().String()
}
// NewFamilyID 生成新的 family_id每次登录都新建
func (s *RefreshTokenService) NewFamilyID() string {
return uuid.New().String()
}
// Create 持久化一条 refreshToken 记录
// - 登录场景:传入新的 familyID + absolutenow + 绝对过期天数)
// - 刷新场景:复用旧 familyID 与旧 absolute保证滑动续期不能突破上限
func (s *RefreshTokenService) Create(jti, familyID, userUUID, userType string,
expiresAt, absoluteExpiresAt time.Time, ua, ip string) error {
db, err := database.GetDB()
if err != nil {
return err
}
rec := models.RefreshToken{
JTI: jti,
FamilyID: familyID,
UserUUID: userUUID,
UserType: userType,
IssuedAt: time.Now(),
ExpiresAt: expiresAt,
AbsoluteExpiresAt: absoluteExpiresAt,
UserAgent: ua,
IP: ip,
}
return db.Create(&rec).Error
}
// FindByJTI 根据 jti 查询
func (s *RefreshTokenService) FindByJTI(jti string) (*models.RefreshToken, error) {
db, err := database.GetDB()
if err != nil {
return nil, err
}
var rec models.RefreshToken
if err := db.Where("jti = ?", jti).First(&rec).Error; err != nil {
return nil, err
}
return &rec, nil
}
// RevokeFamily 撤销整个 family 下所有未撤销的 refreshToken用于重用检测/登出)
func (s *RefreshTokenService) RevokeFamily(familyID string) error {
db, err := database.GetDB()
if err != nil {
return err
}
return db.Model(&models.RefreshToken{}).
Where("family_id = ? AND revoked = ?", familyID, false).
Update("revoked", true).Error
}
// RevokeByJTI 撤销单条 refreshToken一般在轮换时使用 Rotate
func (s *RefreshTokenService) RevokeByJTI(jti string) error {
db, err := database.GetDB()
if err != nil {
return err
}
return db.Model(&models.RefreshToken{}).
Where("jti = ?", jti).
Update("revoked", true).Error
}
// Rotate 标记旧 jti 为已撤销,并记录被替换为新 jti
func (s *RefreshTokenService) Rotate(oldJTI, newJTI string) error {
db, err := database.GetDB()
if err != nil {
return err
}
return db.Model(&models.RefreshToken{}).
Where("jti = ?", oldJTI).
Updates(map[string]interface{}{
"revoked": true,
"replaced_by": newJTI,
}).Error
}
// CleanupExpired 清理过期且过期时间早于 retentionDays 天前的记录
func (s *RefreshTokenService) CleanupExpired(retentionDays int) error {
db, err := database.GetDB()
if err != nil {
return err
}
cutoff := time.Now().AddDate(0, 0, -retentionDays)
return db.Where("expires_at < ?", cutoff).Delete(&models.RefreshToken{}).Error
}
// ErrRefreshNotFound refreshToken 不存在
var ErrRefreshNotFound = errors.New("refresh token not found")
// IsNotFound 判断是否为记录未找到错误
func IsNotFound(err error) bool {
return errors.Is(err, gorm.ErrRecordNotFound)
}