★ 鉴权实践:中间件+Token
面试问法:GoFrame 有内置鉴权方案吗?登录态与接口保护一般怎么设计? 一句话结论:框架不内置账号体系与 JWT,但提供了完整的中间件与上下文机制:登录接口签发 Token,受保护分组挂一个前置鉴权中间件,校验通过后
r.SetCtxVar把身份放进请求上下文,业务侧从 ctx 取;要“注销即时失效”再引入 Redis 黑名单或会话版本号。
关键 API
| 能力 | 用法 |
|---|---|
| 取请求头 | r.GetHeader("Authorization") |
| 终止请求 | r.Response.WriteStatus(401, msg) + r.Exit() |
| 注入身份 | r.SetCtxVar("uid", uid) |
| 业务侧读取 | g.RequestFromCtx(ctx).GetCtxVar("uid") |
| 分组保护 | group.Middleware(Auth),规范路由同样适用 |
最短示例
// 登录成功后签发 Token:可自研签名或引入 JWT 类库,框架不内置
token := SignToken(user.Id)
// 鉴权中间件:解析 "Bearer <token>" 得到 uid
func Auth(r *ghttp.Request) {
raw := r.GetHeader("Authorization")
token := strings.TrimPrefix(raw, "Bearer ")
if token == raw { // 无 Bearer 前缀
r.Response.WriteStatus(401, "unauthorized")
r.Exit()
return
}
uid, err := ParseToken(token) // 验签 + 解析
if err != nil {
r.Response.WriteStatus(401, "invalid token")
r.Exit()
return
}
r.SetCtxVar("uid", uid)
r.Middleware.Next()
}
s.Group("/", func(g *ghttp.RouterGroup) { g.POST("/login", Login) }) // 公开
s.Group("/api", func(g *ghttp.RouterGroup) { // 受保护
g.Middleware(Auth)
g.Bind(new(v1.UserController))
})
业务侧取当前用户:uid := g.RequestFromCtx(ctx).GetCtxVar("uid").Int();controller/service/dao 一路传 ctx,身份随处可得。
设计注意
- 401(未认证)与 403(无权限)要分开;鉴权失败返回建议对齐统一响应格式(code != 0 的 JSON,而非裸文本)。
- 白名单路由(/login、/swagger、健康检查)放公开分组,或在中间件内按路径放行。
- 无状态 Token“注销不即时”:需要时用 Redis 黑名单/会话版本号;纯内部后台可退而用框架内置 Session(r.Session)。
- 中间件顺序:CORS 之后、业务 handler 之前;过期时间由签发端控制(如 2h)。
常见追问
- 追问:Token 放 header 还是 cookie?——header 适合前后端分离与多端;cookie + HttpOnly 能防 XSS 窃取但要防 CSRF,按威胁模型选择。
- 追问:怎么支持“踢人下线 / 单点登录”?——Token 绑定会话版本号或维护 Redis 在线表,每次校验比对,即可即时失效。
记忆点
- 公式:登录发 Token +
group.Middleware(Auth)+ SetCtxVar 传递身份。 - 框架给机制不给方案:签名算法、黑名单、RBAC 权限模型都要业务自己组装。