鉴权中间件:Bearer Token 解析、角色校验、401 统一返回
上一章会签发 JWT,这一章把它做成 Gin 中间件:拦截请求、解析 Authorization 头、把用户信息写进上下文,并统一返回 401。中间件写法以 gin 官方文档的「中间件」章节为准。
1. 中间件骨架
Gin 中间件就是返回 gin.HandlerFunc 的函数,c.Next() 之前是前置校验:
func AuthRequired() gin.HandlerFunc {
return func(c *gin.Context) {
token := BearerToken(c.GetHeader("Authorization"))
if token == "" {
abort401(c, "缺少认证信息")
return
}
claims, err := ParseToken(token) // 复用上一章的校验函数
if err != nil {
abort401(c, "Token 无效或已过期")
return
}
c.Set("userID", claims.UserID)
c.Set("role", claims.Role)
c.Next() // 放行给后续 handler
}
}
func BearerToken(h string) string {
if len(h) > 7 && h[:7] == "Bearer " {
return h[7:]
}
return ""
}
2. 角色校验
再叠一层只关心角色的中间件,用可变参数支持多种角色:
func RequireRoles(roles ...string) gin.HandlerFunc {
return func(c *gin.Context) {
role := c.GetString("role")
for _, r := range roles {
if role == r {
c.Next()
return
}
}
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "无权限访问"})
}
}
只允许管理员:RequireRoles("admin");多个角色都行就多传几个。
3. 401 统一返回
把认证失败的响应收敛成一个函数,保证格式一致、前端好解析:
func abort401(c *gin.Context, msg string) {
c.Header("WWW-Authenticate", `Bearer realm="api"`)
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
"code": 401,
"error": msg,
})
}
4. 挂载到路由
中间件按组生效:公开接口不挂,受保护接口挂:
api := r.Group("/api")
{
api.POST("/login", LoginHandler) // 公开
authed := api.Group("", AuthRequired()) // 需要登录
{
authed.GET("/me", MeHandler)
admin := authed.Group("", RequireRoles("admin"))
admin.DELETE("/users/:id", DeleteUserHandler)
}
}
注意点
- 校验失败必须
AbortWithStatusJSON并return,否则请求会继续往后执行。 c.Set的键建议用常量而不是裸字符串,避免拼写不一致取不到值。- 401(未认证)与 403(无权限)语义要分清,别一律 401。
- 中间件里别做重活(如再查一次库),能靠 Token 声明判断就靠声明。
小结
鉴权中间件 = 前置解析 + 上下文注入 + 统一失败响应。把「谁(userID)+ 什么角色(role)」放进 c,业务 handler 只管取用。角色再多可以引入 RBAC 策略点,但骨架不变。