keyfunc是echo-jwt/v4唯一可控验证入口,因其验证流程中仅此处暴露钩子,需在此拒绝alg:none、手动校验exp/iat/nbf及typ,并将业务级校验(如黑名单)移至后续中间件。

必须用 echo-jwt/v4 的 Config.KeyFunc 实现自定义验证,而不是在 handler 里手动解析 —— 否则会绕过中间件链、丢失上下文、重复校验。
为什么 KeyFunc 是唯一可控入口
echo-jwt/v4 的验证流程是:收到 token → 提取字符串 → 调 KeyFunc 拿密钥 → 交给 jwt.ParseWithClaims 解析 → 校验签名和时间字段 → 塞进 context。整个过程只在 KeyFunc 处暴露钩子,其他环节不可干预。
- 别试图在 handler 里用
jwt.Parse二次解析:此时c.Get("user")已为空,且无法短路响应 - 别改
Config.Claims类型来“加逻辑”:它只负责结构体映射,不参与校验 -
KeyFunc返回密钥前可做任意检查,比如拒绝alg: none、读取kid查 DB、校验 issuer 是否合法
KeyFunc 里必须做的三件事
一个健壮的 KeyFunc 不只是返回密钥,它要承担基础安全守门人职责:
- 检查
token.Header["alg"],直接拒掉"none"—— 否则攻击者可构造无签名 token - 强制启用时间校验:
token.Claims.(jwt.MapClaims).VerifyExpiresAt(time.Now().Unix(), true),否则exp字段被忽略 - 校验
token.Header["typ"] == "JWT",防伪造头部
示例代码片段(注意错误必须显式返回,不能 panic):
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
if typ, ok := token.Header["typ"]; !ok || typ != "JWT" {
return nil, fmt.Errorf("invalid typ header")
}
// 时间校验必须手动触发
if claims, ok := token.Claims.(jwt.MapClaims); ok {
if !claims.VerifyExpiresAt(time.Now().Unix(), true) {
return nil, fmt.Errorf("token expired")
}
}
return []byte("your-secret"), nil
}
如何注入业务级声明验证
时间、签名、算法这些基础验证做完后,真正的业务规则(如角色白名单、IP 绑定、jti 黑名单)得在下一个中间件里做 —— 因为 echo-jwt 已把 *jwt.Token 放进 c.Get("user"),后续中间件可直接断言使用:
- 从
c.Get("user")取出*jwt.Token,再转jwt.MapClaims读"role"、"jti"等字段 - 查 Redis 判断
jti是否在revoked:{jti}中;查 DB 验证用户状态是否 active - 若失败,直接
return echo.NewHTTPError(http.StatusUnauthorized, "xxx"),不要调next(c)
关键点:这个中间件必须注册在 echojwt.WithConfig(...) 之后,顺序错了就拿不到已解析的 token。
容易被忽略的底层陷阱
echo-jwt/v4 底层调的是 jwt-go 的 ParseWithClaims,而它的默认行为极度危险:
-
exp字段默认不校验:除非你在KeyFunc里手动调VerifyExpiresAt,否则过期 token 也能过 -
iat/nbf同理,不调VerifyIssuedAt或VerifyNotBefore就等于没设 -
exp值必须是秒级int64,前端传毫秒时间戳会导致 token “刚签发就过期” - 所有校验函数返回
bool,不是 error —— 忘了 if 判断就等于没校验
真正生效的验证逻辑,90% 都藏在 KeyFunc 里那几行手动调用中,而不是配置项里。










