Files
openteam/server/internal/pkg/jwt/jwt.go
Sakurasan e3ff0d3329 security: 会话可撤销与认证面加固
- users 加 token_version(schema v4):改密/登出递增,该用户全部已发 access/refresh 立即作废;refresh 改轮换式换发新 cookie
- 邀请码改精确匹配 + 注册成功后消费(一码一用),修复子串匹配让任意后缀放行、可无限薅体验金
- 生产校验拦下 .env.example 占位密钥;OT_ENV 枚举校验(拼错不再静默跳过断言);生产强制 COOKIE_SECURE;非 development 拒绝默认密码播种管理员
- WebAuthn 会话启用过期校验 + 4096 容量上限(未认证 login/begin 可无限撑爆内存),begin/complete 挂登录级限速
- /api/v1 挂 1MB 请求体上限;login 先限 IP 桶再解析 body;全局安全响应头 + CSP
- 连接池配置(PG 30/SQLite 8);SQLite DSN 自动追加 WAL + busy_timeout(驱动只认 _pragma,旧注释的 _journal_mode 写法不生效)
2026-10-29 02:56:46 +08:00

76 lines
2.1 KiB
Go
Raw Permalink 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 jwt 访问令牌(短时,存内存)与刷新令牌(HttpOnly Cookie)签发/校验。
package jwt
import (
"errors"
"time"
"github.com/golang-jwt/jwt/v5"
)
// Claims 用户声明;Subject 字段区分 "access" / "refresh"。
// Ver 是用户级会话版本号:改密/登出时递增,旧 token 立即失效(可撤销性)。
type Claims struct {
UserID uint64 `json:"uid"`
Username string `json:"uname"`
Role string `json:"role"`
Ver int `json:"ver"`
jwt.RegisteredClaims
}
type Manager struct {
secret []byte
issuer string
accessTTL time.Duration
refreshTTL time.Duration
}
func NewManager(secret, issuer string, accessTTL, refreshTTL time.Duration) *Manager {
return &Manager{secret: []byte(secret), issuer: issuer, accessTTL: accessTTL, refreshTTL: refreshTTL}
}
func (m *Manager) AccessTTL() time.Duration { return m.accessTTL }
func (m *Manager) RefreshTTL() time.Duration { return m.refreshTTL }
// Sign 签发 token;typ 取 "access" / "refresh",ver 为签发时的会话版本。
func (m *Manager) Sign(userID uint64, username, role, typ string, ver int) (string, time.Time, error) {
ttl := m.accessTTL
if typ == "refresh" {
ttl = m.refreshTTL
}
now := time.Now()
exp := now.Add(ttl)
claims := Claims{
UserID: userID,
Username: username,
Role: role,
Ver: ver,
RegisteredClaims: jwt.RegisteredClaims{
Issuer: m.issuer,
Subject: typ,
IssuedAt: jwt.NewNumericDate(now),
ExpiresAt: jwt.NewNumericDate(exp),
},
}
tok := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
s, err := tok.SignedString(m.secret)
return s, exp, err
}
var ErrInvalidToken = errors.New("invalid token")
// Parse 校验签名与有效期。
func (m *Manager) Parse(token string) (*Claims, error) {
claims := &Claims{}
tok, err := jwt.ParseWithClaims(token, claims, func(t *jwt.Token) (any, error) {
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, ErrInvalidToken
}
return m.secret, nil
})
if err != nil || !tok.Valid {
return nil, ErrInvalidToken
}
return claims, nil
}