★ 鉴权设计:Token 校验中间件与 RBAC 思路
一句话结论:鉴权拆成两层——认证(Authentication:你是谁)与授权(Authorization:你能干什么)。认证用 Token 校验中间件:从 Authorization: Bearer 头取 JWT,固定算法白名单验签、校验过期等声明,把用户身份写进 gin.Context,白名单路由不挂中间件。授权按 RBAC 模型落地:用户→角色→权限(操作+资源),权限数据登录后缓存或放进 JWT 声明,handler 按所需权限码校验;同时必须做资源级越权检查(IDOR),不能只验“登录了”。
Token 校验中间件
var jwtKey = []byte(os.Getenv("JWT_SECRET")) // 32+ 字节,勿硬编码
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
h := c.GetHeader("Authorization")
if !strings.HasPrefix(h, "Bearer ") { abort401(c); return }
token, err := jwt.ParseWithClaims(strings.TrimPrefix(h, "Bearer "),
&Claims{}, func(t *jwt.Token) (any, error) {
if t.Method != jwt.SigningMethodHS256 { // 防算法混淆
return nil, errors.New("unexpected signing method")
}
return jwtKey, nil
}, jwt.WithValidMethods([]string{"HS256"}))
if err != nil || !token.Valid { abort401(c); return }
claims := token.Claims.(*Claims)
c.Set("uid", claims.UserID)
c.Set("roles", claims.Roles)
c.Next()
}
}
- 用 jwt.WithValidMethods 固定算法,杜绝 alg=none、RS/HS 混淆攻击。
- 过期/登出吊销:短有效期 token + refresh token 轮换;要即时吊销就维护 Redis 黑名单(登出/改密入黑名单,TTL=剩余有效期)。
RBAC 落地思路
| 层 | 内容 | 说明 |
|---|---|---|
| 模型 | 用户、角色、权限 + 两张关联表 | 权限=资源+操作(如 order:create) |
| 载入 | 登录后缓存 user:{id}:perms,或放 JWT 声明 | 缓存即时生效但每次查询;JWT 免查询但不即时、易膨胀 |
| 校验 | 路由级中间件或 handler 内 CheckPerm | 路由登记“该操作需要的权限码”,集中比对 |
| 越权 | 资源归属校验 | 查 order 前校验 order.UserID == uid,防 IDOR |
注册与使用
r.POST("/api/v1/login", login) // 公开
api := r.Group("/api/v1", AuthMiddleware())
api.GET("/orders", RequirePerm("order:read"), listOrders)
api.POST("/orders/:id/cancel", RequirePerm("order:cancel"), cancelOrder)
追问记忆点
- 追问:JWT 放 Redis 黑名单和直接查库角色哪个快?——黑名单只需一次 Redis GET;但每次请求都验权限可以再叠加进程内缓存+TTL。
- 追问:认证过了还要防什么?——越权:同接口横向(他人资源)、纵向(低角色调高权限动作),都要资源归属与权限码双校验。
- 追问:token 存哪更安全?——SPA 存内存/HttpOnly Cookie(防 XSS 偷),Cookie 方案注意 CSRF(配 SameSite)。
- 记忆点:认证管身份、授权管权限;验签锁算法、声明校验过期;RBAC=角色挂权限,权限码登记路由;别忘了 IDOR。