初始化项目:基础设施 + 用户认证 + 后台管理系统 + AGPL 3.0 许可
This commit is contained in:
123
internal/service/token_service.go
Normal file
123
internal/service/token_service.go
Normal file
@ -0,0 +1,123 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"metazone.cc/metalab/internal/common"
|
||||
"metazone.cc/metalab/internal/config"
|
||||
"metazone.cc/metalab/internal/model"
|
||||
"metazone.cc/metalab/internal/repository"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
// TokenService JWT 令牌签发与刷新
|
||||
// 独立于认证业务逻辑,供 AuthService 和其他需要签发令牌的服务使用
|
||||
type TokenService struct {
|
||||
cfg *config.Config
|
||||
userRepo *repository.UserRepo
|
||||
}
|
||||
|
||||
// NewTokenService 构造函数
|
||||
func NewTokenService(cfg *config.Config, userRepo *repository.UserRepo) *TokenService {
|
||||
return &TokenService{cfg: cfg, userRepo: userRepo}
|
||||
}
|
||||
|
||||
// BuildAccessToken 构建 access JWT(含 uid/email/username/role/ver/exp/iat)
|
||||
// ver = user.TokenVersion,用于即时吊销:版本号不匹配则拒绝
|
||||
func (ts *TokenService) BuildAccessToken(user *model.User) (string, error) {
|
||||
expire := time.Duration(ts.cfg.JWT.AccessExpire) * time.Minute
|
||||
now := time.Now()
|
||||
expAt := now.Add(expire)
|
||||
log.Printf("[BuildAccessToken] AccessExpire=%d min → expire=%v | now=%v | exp=%v (%d) | iat=%d ver=%d",
|
||||
ts.cfg.JWT.AccessExpire, expire, now, expAt, expAt.Unix(), now.Unix(), user.TokenVersion)
|
||||
claims := jwt.MapClaims{
|
||||
"uid": user.ID,
|
||||
"email": user.Email,
|
||||
"username": user.Username,
|
||||
"role": user.Role,
|
||||
"ver": user.TokenVersion,
|
||||
"exp": expAt.Unix(),
|
||||
"iat": now.Unix(),
|
||||
}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
return token.SignedString([]byte(ts.cfg.JWT.Secret))
|
||||
}
|
||||
|
||||
// BuildRefreshToken 构建 refresh JWT(含 purpose:"refresh" 防止 access 冒充)
|
||||
// rememberMe=true → 使用 remember_expire(30天);false → 使用 refresh_expire(7天,session 模式)
|
||||
// ver = user.TokenVersion,刷新时校验:版本号不匹配则拒绝
|
||||
func (ts *TokenService) BuildRefreshToken(user *model.User, rememberMe bool) (string, error) {
|
||||
var expireHours int
|
||||
if rememberMe {
|
||||
expireHours = ts.cfg.JWT.RememberExpire
|
||||
} else {
|
||||
expireHours = ts.cfg.JWT.RefreshExpire
|
||||
}
|
||||
expire := time.Duration(expireHours) * time.Hour
|
||||
now := time.Now()
|
||||
claims := jwt.MapClaims{
|
||||
"uid": user.ID,
|
||||
"email": user.Email,
|
||||
"username": user.Username,
|
||||
"role": user.Role,
|
||||
"ver": user.TokenVersion,
|
||||
"exp": now.Add(expire).Unix(),
|
||||
"iat": now.Unix(),
|
||||
"purpose": "refresh",
|
||||
}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
return token.SignedString([]byte(ts.cfg.JWT.Secret))
|
||||
}
|
||||
|
||||
// RefreshAccessToken 用 refresh token 换取新的 access token
|
||||
// 校验 token_version:若 DB 中版本已递增,拒绝刷新 → 即时吊销
|
||||
func (ts *TokenService) RefreshAccessToken(refreshTokenStr string) (string, *model.User, error) {
|
||||
if refreshTokenStr == "" {
|
||||
return "", nil, ErrTokenInvalid
|
||||
}
|
||||
|
||||
token, err := jwt.Parse(refreshTokenStr, func(t *jwt.Token) (interface{}, error) {
|
||||
return []byte(ts.cfg.JWT.Secret), nil
|
||||
})
|
||||
if err != nil || !token.Valid {
|
||||
return "", nil, ErrTokenExpired
|
||||
}
|
||||
|
||||
claims, ok := token.Claims.(jwt.MapClaims)
|
||||
if !ok {
|
||||
return "", nil, ErrTokenInvalid
|
||||
}
|
||||
|
||||
// 只接受 refresh 用途的 token,防止 access token 被用于刷新
|
||||
if purpose, _ := claims["purpose"].(string); purpose != "refresh" {
|
||||
return "", nil, ErrTokenInvalid
|
||||
}
|
||||
|
||||
uid := uint(claims["uid"].(float64))
|
||||
tokenVer := int(claims["ver"].(float64))
|
||||
|
||||
// 即时吊销检查 + 获取最新用户数据(角色/状态可能在 JWT 签发后已变更)
|
||||
// 用 FindByIDForAuth 而非从 claims 重建,确保 access token 承载最新数据
|
||||
user, err := ts.userRepo.FindByIDForAuth(uid)
|
||||
if err != nil {
|
||||
return "", nil, common.ErrTokenRevoked
|
||||
}
|
||||
|
||||
if tokenVer != user.TokenVersion {
|
||||
log.Printf("[RefreshAccessToken] REVOKED: uid=%d tokenVer=%d dbVer=%d", uid, tokenVer, user.TokenVersion)
|
||||
return "", nil, common.ErrTokenRevoked
|
||||
}
|
||||
|
||||
// 防止封禁用户通过 refresh 续期
|
||||
if user.Status == model.StatusBanned {
|
||||
return "", nil, common.ErrUserBanned
|
||||
}
|
||||
|
||||
accessToken, err := ts.BuildAccessToken(user)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return accessToken, user, nil
|
||||
}
|
||||
Reference in New Issue
Block a user