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 写法不生效)
This commit is contained in:
1 parent
d6db0f8162
commit
e3ff0d3329
14 files changed
+392
-37
No files matched your search
@@ -33,11 +33,19 @@ type Service struct {
|
||||
sessions map[string]webauthn.SessionData // keyed by challenge
|
||||
}
|
||||
|
||||
// maxSessions challenge 会话上限:login/begin 无需认证,不设上限的话每次调用
|
||||
// 都会向 map 永久写入一条(只有完成认证才删),持续打可把进程内存打爆。
|
||||
const maxSessions = 4096
|
||||
|
||||
func New(db *gorm.DB, cfg Config) (*Service, error) {
|
||||
wa, err := webauthn.New(&webauthn.Config{
|
||||
RPDisplayName: cfg.Name,
|
||||
RPID: cfg.RPID,
|
||||
RPOrigins: []string{cfg.Origin},
|
||||
Timeouts: webauthn.TimeoutsConfig{
|
||||
Login: webauthn.TimeoutConfig{Enforce: true, Timeout: 5 * time.Minute, TimeoutUVD: 5 * time.Minute},
|
||||
Registration: webauthn.TimeoutConfig{Enforce: true, Timeout: 5 * time.Minute, TimeoutUVD: 5 * time.Minute},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -224,8 +232,27 @@ func (s *Service) updateCredential(userID uint64, cred *webauthn.Credential) err
|
||||
|
||||
func (s *Service) storeSession(session *webauthn.SessionData) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
// 到容量上限先清一轮已过期会话;仍满则淘汰最早过期的(未过期说明正在被
|
||||
// 正常使用,丢最不紧迫的)。配合 Enforce 过期校验,map 大小有硬上界。
|
||||
if len(s.sessions) >= maxSessions {
|
||||
now := time.Now()
|
||||
var oldestKey string
|
||||
var oldestExp time.Time
|
||||
for k, v := range s.sessions {
|
||||
if now.After(v.Expires) {
|
||||
delete(s.sessions, k)
|
||||
continue
|
||||
}
|
||||
if oldestKey == "" || v.Expires.Before(oldestExp) {
|
||||
oldestKey, oldestExp = k, v.Expires
|
||||
}
|
||||
}
|
||||
if len(s.sessions) >= maxSessions && oldestKey != "" {
|
||||
delete(s.sessions, oldestKey)
|
||||
}
|
||||
}
|
||||
s.sessions[session.Challenge] = *session
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
func (s *Service) takeSession(challenge string) (webauthn.SessionData, bool) {
|
||||
|
||||
Reference in new issue
Block a user