Gin 实战 11:接口鉴权中间件
登录负责签发 Token,接口鉴权负责在请求进入业务代码前校验 Token,并把当前用户写入 gin.Context。
1. 请求头格式
text
Authorization: Bearer <accessToken>2. 解析 Token
go
func ParseToken(tokenString string, secret string) (*Claims, error) {
// ParseWithClaims 会解析 token,并把 payload 填充到 Claims 结构体。
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (any, error) {
// 返回签名密钥,JWT 库会用它校验签名是否可信。
return []byte(secret), nil
})
if err != nil {
return nil, err
}
// 类型断言失败或 token.Valid=false,都说明 Token 不可信。
claims, ok := token.Claims.(*Claims)
if !ok || !token.Valid {
return nil, errors.New("无效 Token")
}
return claims, nil
}3. Gin 中间件
go
// CurrentUserKey 是写入 gin.Context 的 key。
// 后续 Handler 可以通过它获取当前登录用户。
const CurrentUserKey = "currentUser"
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 1. 从请求头读取 Authorization。
header := c.GetHeader("Authorization")
if !strings.HasPrefix(header, "Bearer ") {
c.Error(errs.BadRequest("Token 无效"))
c.Abort()
return
}
// 2. 去掉 Bearer 前缀,拿到真正的 JWT 字符串。
tokenString := strings.TrimPrefix(header, "Bearer ")
// 3. 校验签名和过期时间,并解析用户信息。
claims, err := ParseToken(tokenString, config.Cfg.Security.JWT.SecretKey)
if err != nil {
c.Error(errs.BadRequest("Token 无效或已过期"))
c.Abort()
return
}
// 4. 写入上下文,后续 Handler 就能拿到当前用户。
c.Set(CurrentUserKey, claims)
// 5. 放行请求,继续执行后续 Handler。
c.Next()
}
}4. 路由分组
go
func Register(r *gin.Engine) {
api := r.Group("/api/v1")
// 登录和验证码不需要 Token,所以挂在公开路由上。
auth.RegisterRoutes(api) // 登录、验证码公开
// 受保护路由统一挂认证中间件。
authorized := api.Group("")
authorized.Use(middleware.AuthMiddleware())
{
user.RegisterRoutes(authorized)
}
}5. 获取当前用户
go
func CurrentUser(c *gin.Context) (*Claims, bool) {
// 从 gin.Context 读取认证中间件写入的用户信息。
value, ok := c.Get(CurrentUserKey)
if !ok {
return nil, false
}
// 做类型断言,避免取到错误类型导致 panic。
user, ok := value.(*Claims)
return user, ok
}到这里,用户 CRUD 已经从公开接口变成登录后才能访问的受保护接口。
