Manager(每应用一个,GlobalSessions)
│ 持有 provider(按配置选择的引擎)
▼
Provider(每引擎一个,"会话仓库")
│ SessionRead(sid) 创建/取出
▼
Store(每请求一个)
│ Get/Set/Delete/Flush…
▼
SessionRelease(请求结束,写回仓库)
接口定义(session.go)
type Store interface { // :46-54
Set(ctx, key, val) error
Get(ctx, key) interface{}
Delete(ctx, key) error
SessionID(ctx) string
SessionReleaseIfPresent(ctx, w) # 2.x 新增:防御性释放
SessionRelease(ctx, w) # 写回持久层
Flush(ctx) error # 清空
}
type Provider interface { // :58-66
SessionInit(ctx, gcLifetime, config) error # 引擎初始化(连接串)
SessionRead(sid) (Store, error)
SessionExist(sid) (bool, error)
SessionRegenerate(oldSID, newSID) (Store, error)
SessionDestroy(sid) error
SessionAll() int
SessionGC(ctx) # 引擎各自的清理
}
注册表(session.go:75-89)
provides map[string]Provider + Register(name, provide);12 个引擎各自 init 注册:cookie(file: sess_cookie.go)、file、mem、redis、redis_cluster、redis_sentinel、memcache、mysql、postgres、couchbase、ledis、ssdb。
- Manager(session.go:92-144)
type Manager struct {
provider Provider
config *ManagerConfig // SessionName/CookieLifeTime/Gclifetime/Domain/SameSite...
}
NewManager(provideName, cf)(109-144):查 provides、SessionInit(provider)、校验 header 模式 MIME。web 侧 hooks.go registerSession(47-77):按 BConfig 构建 ManagerConfig → 创建 GlobalSessions → go GlobalSessions.GC()。
2.1 sid 从哪来:getSid(158-184)
三级回退:Cookie → URL query → HTTP header(后两者需 SessionEnableSidInURLQuery/HeaderIP 开启)。这就是"App 客户端无 cookie 也能带 session"的实现。
2.2 SessionStart(188-238)——每请求入口
getSid 取 sid
├─ 有效:provider.SessionRead(sid)
└─ 无效:crypto/rand 生成 16 字节 hex(SessionIDPrefix 前缀,345-352)
provider.SessionRead(new sid)
写 cookie:HttpOnly(!SessionDisableHTTPOnly)/Secure/Domain/SameSite/CookieLifeTime
(SessionEnableSidInHTTPHeader 时也写响应头)
返回 Store → serveHttp 放入 ctx.Input.CruSession
2.3 SessionRegenerateID(284-333)
换 sid 保数据:provider.SessionRegenerate(oldsid, newsid) + 重写 cookie——防会话固定攻击的服务端支持。
2.4 GC 自调度(278-281)——优雅的技巧
func (manager *Manager) GC() {
manager.provider.SessionGC(context.Background())
time.AfterFunc(manager.config.Gclifetime, func() { manager.GC() })
}
不用常驻 ticker,每次执行完后安排下一次:改 Gclifetime 配置能在下轮生效,且无泄漏。值得抄的模式。
2.5 会话释放的演进
-
v2.0:
defer SessionRelease(ctx, w) -
近期修复(git log: "use request context for session release")新增
SessionReleaseIfPresent:以请求 context 为准,请求 ctx 已取消时也能安全释放,避免客户端断连导致会话丢写。各引擎同步实现。
- cookie 引擎:客户端会话的安全样本(sess_cookie.go + sess_utils.go)
存储介质是 cookie 本身(4KB 限制),数据链:
写入:map[string]interface{}
→ gob 编码(EncodeGob,47-70)
→ AES-CTR 加密(encrypt,87-116;密钥=hash(session 安全串))
→ base64
→ 编码 cookie: name|date|value| 三段 base64 + HMAC-SHA256 签名(encodeCookie,118-152)
读取:decodeCookie(154-188)
→ 校验 MAC(防篡改)
→ 校验 date 在 [now-gcmaxlifetime, now](防重放旧值)
→ 解 base64 → AES 解密 → gob 解码
为什么三层:签名防篡改、加密防偷看(明文 key)、时间戳防重放。虽然单值 cookie 一般不建议存敏感数据,但这套编码是 beego 自身 SecureCookie(context 包)的同源实现。
- file 引擎走读(sess_file.go)——引擎范本
type FileSessionStore struct { sid; lock sync.RWMutex; values map[...]; ... }
func (fs *FileSessionStore) SessionRelease(ctx, w) {
// gob 编码 values → 写回 sid 文件(带文件锁)
}
type FileProvider struct { maxlifetime; savePath }
func (fp *FileProvider) SessionRead(sid) {
// 打开/创建 savePath/sid,独占锁;空文件初始化
}
func (fp *FileProvider) SessionGC(ctx) {
// filepath.Walk:按 ModTime > maxlifetime 删除过期文件(gcpath,302)
}
redis/mysql 引擎同构:ProviderConfig = 连接串,SessionRead 建连接取 hash/行,SessionRelease 写回,GC 用 EXPIRE/DELETE。读引擎源码的套路:只看 SessionInit/Read/Release/GC 四个方法。
- 与 serveHttp 的协作(S05 回顾)
步骤 7:FindRouter 预判 sessionOn(路由级开关)
GlobalSessions.SessionStart → ctx.Input.CruSession
defer SessionReleaseIfPresent
控制器/过滤器:Get/Set(内存 values)
步骤 17 后:defer 触发 SessionRelease → provider 写回
写延迟语义:会话修改在请求结束才持久化——意味着"同一用户并发两请求"存在丢失更新(file 引擎整文件覆盖)。需要强一致计数请用 redis 引擎的原子命令或 DB 行锁。
- 内存引擎的 map 锁(sess_mem.go)
CookieSessionStore/mem 引擎:RWMutex 保护 values;SessionAll 遍历计数。单机开发够用,多副本部署必换外部引擎(使用篇 Day08 坑 2 的根源)。
- 设计赏析
-
三级接口职责清晰:Manager(策略/sid/cookie)、Provider(仓储)、Store(读写);新引擎 4-7 个方法搞定。
-
GC 的 AfterFunc 自调度:见 2.4。
-
sid 多通道(cookie/query/header):移动端友好,代价是 XSS/URL 泄漏面变大——默认只开 cookie。
-
防御性释放(SessionReleaseIfPresent):一个真实生产 bug 催化的 API 演进,读 git 历史比读代码更能理解它为什么存在。
beego Session 设计清晰,引擎可插拔,sid 多通道适合 App 客户端。但写延迟语义下并发需注意,推荐 redis 引擎保证强一致。本文是理解 beego 会话机制与生产避坑的优质源码解读。