revel框架无官方jwt组件,需基于golang-jwt/jwt/v5手动实现鉴权中间件;必须在init()中注册filter,按路径白名单控制拦截范围,并注意密钥安全、算法校验与时钟同步。

Revel 中 JWT 鉴权必须自己写中间件,没有官方 jwt 组件
Revel 框架本身不提供 @midwayjs/jwt 或 spring-boot-starter-jwt 这类开箱即用的 JWT 封装。它只暴露 revel.Interceptor 和 revel.Filter 机制,JWT 的签发、解析、校验全得靠你自己组合 github.com/golang-jwt/jwt/v5(推荐 v5)或 github.com/dgrijalva/jwt-go(已归档,慎用)来实现。
常见错误是试图把 Spring Boot 那套拦截器配置直接搬过来——比如照抄 @PreAuthorize 注解或 auth:api 中间件名,结果编译报错或根本没生效。Revel 的 filter 是函数式链式调用,不是注解驱动。
- 必须在
app/init.go的init()函数里注册自定义 filter,顺序很重要:鉴权 filter 要在路由匹配之后、controller 执行之前 - 不能依赖全局单例
*jwt.Token,每次请求都需独立解析,否则并发下会出错 - payload 解析后建议存入
revel.Controller.Params或revel.Controller.Request.Context,避免 controller 层重复解析
如何安全生成和校验 JWT Token(用 github.com/golang-jwt/jwt/v5)
别用过时的 jwt-go(有 CVE-2020-26160 签名绕过漏洞),golang-jwt/jwt/v5 是当前维护最活跃、API 更清晰的替代品。密钥必须从环境变量或配置文件读取,禁止硬编码在代码里。
生成 token 示例(登录成功后):
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"uid": user.ID,
"exp": time.Now().Add(24 * time.Hour).Unix(),
"iat": time.Now().Unix(),
})
signedToken, err := token.SignedString([]byte(os.Getenv("JWT_SECRET")))
if err != nil {
// 返回 500
}
校验 token 示例(filter 中):
authHeader := c.Request.Header.Get("Authorization")
if len(authHeader) == 0 || !strings.HasPrefix(authHeader, "Bearer ") {
c.Result = revel.ERROR
return
}
tokenStr := strings.TrimPrefix(authHeader, "Bearer ")
token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) {
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
}
return []byte(os.Getenv("JWT_SECRET")), nil
})
if err != nil || !token.Valid {
c.Result = revel.ERROR
return
}
// 提取 claims 并注入上下文
if claims, ok := token.Claims.(jwt.MapClaims); ok {
c.Params.Set("uid", int64(claims["uid"].(float64)))
}
-
SigningMethodHS256是最常用且够用的选择;如需更高安全性,可用SigningMethodRS256配合 RSA 私钥签名、公钥校验 - 务必检查
t.Method类型,防止算法混淆攻击(alg=none 攻击) -
jwt.Parse不会自动验证exp,但token.Valid会——前提是传入的 keyFunc 返回了有效密钥
Revel filter 鉴权逻辑怎么挂载才不漏掉接口
很多人把 filter 加在 revel.BEFORE,结果静态资源、健康检查、登录接口也被拦住,导致无限重定向或无法登录。正确做法是按路径白名单控制。
推荐在 app/init.go 中这样注册:
revel.Interceptors = append(revel.Interceptors, revel.BEFORE, func(c *revel.Controller, fc []revel.Filter) revel.Result {
// 白名单:跳过 /login、/health、/public 等无需鉴权的路径
path := c.Request.URL.Path
if strings.HasPrefix(path, "/login") ||
strings.HasPrefix(path, "/health") ||
strings.HasPrefix(path, "/public") {
return nil
}
// 其他路径走鉴权逻辑
if authResult := checkJWT(c); authResult != nil {
return authResult
}
return nil
})
- 不要用正则匹配路径,简单前缀判断足够快且不易出错
- 如果用了 API 版本前缀(如
/v1/users),白名单要覆盖到/v1/login,否则登录接口会被自己拦死 - filter 返回非
nil的revel.Result(如revel.ERROR或revel.Redirect)才会中断执行流
JWT 失效、续签、退出这些事 Revel 怎么处理
Revel 没有内置 refresh token 机制或黑名单存储,这些都得你补全。JWT 本身“无状态”,所以退出只能靠前端删掉本地 token;而续签需要额外接口返回新 token,并由前端主动更新。
- 退出逻辑很简单:前端清空 localStorage 中的 token,后端无需做任何事(除非你额外实现 Redis 黑名单,那就要在 filter 里多一次查库)
- 续签接口建议设计为 POST
/auth/refresh,接收旧 token,校验refresh字段(需在初始 token 里预留该 claim),签发新 access token - 注意
exp时间单位是秒(Unix 时间戳),别误用毫秒,否则 token 一秒就过期 - 如果用了
time.Now().Add(2 * time.Hour),记得在测试时用固定时间点验证,避免本地时区偏差导致 token 看似无效
最容易被忽略的是时钟漂移问题:Revel 服务器与前端设备系统时间如果不一致超过 token 的 nbf(not before)或 exp 容忍窗口,校验就会失败。生产环境务必确保 NTP 同步。











