应使用中间件统一鉴权而非在每个handler中硬编码:解析Authorization头或session,校验JWT的exp/iat等标准声明,将用户信息注入context;结合gorilla/mux按角色分路由并配置requireRole中间件;权限数据化存储于roles、permissions及关联表,按endpoint+method粒度缓存鉴权结果。

用 net/http + 中间件做基础权限拦截
Go 原生 http.ServeMux 不带鉴权能力,得靠中间件手动拦截。核心思路是:在请求进入业务 handler 前,检查 request.Header.Get("Authorization") 或 session/cookie,验证通过才 next.ServeHTTP(w, r),否则返回 401 或 403。
常见错误是把权限逻辑写进每个 handler 里,导致重复、漏判、难维护。正确做法是抽成独立函数:
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token == "" {
http.Error(w, "missing auth header", http.StatusUnauthorized)
return
}
// 解析 JWT 或查 session store
userID, role, err := validateToken(token)
if err != nil || role == "" {
http.Error(w, "invalid token", http.StatusForbidden)
return
}
// 注入上下文,供后续 handler 使用
ctx := context.WithValue(r.Context(), "user_id", userID)
ctx = context.WithValue(ctx, "role", role)
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
})
}
注册时链式调用:http.Handle("/admin/", authMiddleware(http.HandlerFunc(adminHandler)))。注意路径匹配规则 —— /admin/ 会匹配 /admin/users,但 /admin(不带尾斜杠)不会被匹配。
用 gorilla/mux 实现角色路由级控制
gorilla/mux 支持为 route 设置自定义 matcher,比原生 mux 更适合做 RBAC。关键不是“加中间件”,而是“按角色注册不同子路由器”。
立即学习“go语言免费学习笔记(深入)”;
- 管理员路由全部挂到
adminRouter,并统一加requireRole("admin")中间件 - 普通用户路由挂到
userRouter,中间件检查role == "user" || role == "admin" - 避免用
if role == "admin" { ... } else if role == "user" { ... }在 handler 内硬编码分支
示例中容易踩的坑:误以为 router.Use() 能对子 router 生效 —— 实际上它只影响直接注册在该 router 上的 handler,子 router 需单独调用 Use()。
JWT 验证时别忽略 exp 和 iat 校验
很多 Go 项目用 golang-jwt/jwt(v5+)解析 token,但只校验签名,漏掉时间字段,导致过期 token 仍被接受。
必须显式启用标准声明验证:
token, err := jwt.ParseWithClaims(
tokenString,
&Claims{},
func(token *jwt.Token) (interface{}, error) {
return []byte(jwtSecret), nil
},
)
if err != nil {
return nil, err
}
if !token.Valid {
return nil, errors.New("invalid token")
}
// ⚠️ 这步不能少:检查 exp/iat/nbf
claims, ok := token.Claims.(*Claims)
if !ok || !claims.VerifyExpiresAt(time.Now().Unix(), true) {
return nil, errors.New("token expired")
}
VerifyExpiresAt 第二个参数设为 true 才启用严格校验;iat(issued at)建议也校验,防止回放攻击 —— 比如限制 token 只能在签发后 5 分钟内使用。
数据库权限表设计要支持动态策略
硬编码 if role == "admin" { allow } else { deny } 无法应对运营需求变化。真实系统应把权限落地为数据:一张 roles 表、一张 permissions 表、一张 role_permissions 关联表。
每次请求鉴权时,不是查“用户角色”,而是查“该用户能访问哪些 endpoint + method”:
- 缓存 key 可设为
perm:,比如: : perm:123:POST:/api/v1/orders - 避免每次请求都查库,用
redis或go-cache缓存结果,TTL 设为 5–10 分钟 - 权限变更时主动清缓存,而不是等自然过期
最常被忽略的是 HTTP 方法粒度 —— 同一个路径,GET /users 可能全员可读,但 DELETE /users/123 必须 owner 或 admin。权限表里 method 字段不能省。










