鉴权实践:Token 解析与路由分组保护

多数业务 API 都要求“先登录、再访问”:客户端带 Token,服务端验签并识别用户。GoFrame 没有强制规定鉴权方案,官方推荐的组合是路由分组 + 中间件:登录接口公开,其余接口挂在受保护分组下,由前置中间件统一完成 Token 校验。Token 签发算法框架不内置,一般用 JWT 类三方库或自实现签名,本章演示自实现的 HMAC Token(教学演示,生产可替换为 JWT)。

基本概念

  • 前置中间件在业务 handler 之前执行,决定“放行 / 拒绝”。
  • r.GetHeader("Authorization") 取请求头;r.SetCtxVar("uid", v) 把解析结果放进上下文,业务侧用 g.RequestFromCtx(ctx).GetCtxVar("uid") 读取。
  • 校验失败的标准做法:返回 401 并 r.Exit() 终止后续执行。

登录并签发 Token

package main
import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/base64"
    "encoding/json"
    "time"
)
var secret = []byte("please-change-me") // 生产放配置/密钥管理

// SignToken 签发:payload + HMAC-SHA256 签名,演示用,可替换为 JWT
func SignToken(uid int64) string {
    payload, _ := json.Marshal(map[string]interface{}{
        "uid": uid, "exp": time.Now().Add(2 * time.Hour).Unix(),
    })
    b64 := base64.RawURLEncoding.EncodeToString(payload)
    mac := hmac.New(sha256.New, secret)
    mac.Write([]byte(b64))
    return b64 + "." + base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
}

中间件解析 Token 并保护分组

package main
import (
    "github.com/gogf/gf/v2/frame/g"
    "github.com/gogf/gf/v2/net/ghttp"
)

// Auth 前置中间件:解析 Authorization: Bearer <token>
func Auth(r *ghttp.Request) {
    token := r.GetHeader("Authorization")
    const prefix = "Bearer "
    if len(token) <= len(prefix) || token[:len(prefix)] != prefix {
        r.Response.WriteStatus(401, "unauthorized")
        r.Exit()
        return
    }
    uid, err := ParseToken(token[len(prefix):]) // 验签并解析,返回 uid
    if err != nil {
        r.Response.WriteStatus(401, "invalid token")
        r.Exit()
        return
    }
    r.SetCtxVar("uid", uid) // 注入上下文,供后续业务使用
    r.Middleware.Next()
}

func main() {
    s := g.Server()
    s.Group("/", func(group *ghttp.RouterGroup) {
        group.POST("/login", LoginHandler) // 公开:登录接口
    })
    s.Group("/api", func(group *ghttp.RouterGroup) {
        group.Middleware(Auth) // 受保护:/api 下所有接口先过 Auth
        group.GET("/profile", ProfileHandler)
    })
    s.SetPort(8199)
    s.Run()
}

业务处理器读取当前用户:

func ProfileHandler(r *ghttp.Request) {
    uid := g.RequestFromCtx(r.Context()).GetCtxVar("uid")
    r.Response.WriteJson(g.Map{"uid": uid.Val()})
}

规范路由下的鉴权

规范路由(group.Bind)同样把鉴权中间件挂到分组即可;校验失败的返回建议对齐统一响应格式(如 code != 0 的 JSON,而非裸 401 文本),可参考“统一响应与错误码”章节。框架也内置 Session 能力(r.Session),简单后台系统可直接用 Session 方案,官方文档均有对应章节。

注意点

  • Token 过期与“注销即失效”是两件事:无状态 Token 注销即时性差,必要时引入 Redis 黑名单。
  • 鉴权放在 CORS/日志等通用中间件之后、业务 handler 之前;401 是“未认证”,403 是“无权限”,要区分清楚。

小结

鉴权骨架 = 登录发 Token + group.Middleware(Auth) 保护分组 + SetCtxVar 传递身份。无论换 JWT 库还是自研签名,中间件边界与 401 处理都是同一套,掌握后可平滑替换实现。

笔记加载中…