jwt鉴权失败默认无日志,因echo-jwt中间件直接返回401且不触发日志;需通过自定义中间件或全局错误处理器捕获*echo.httperror并仅对401做脱敏日志(如记录ip、路径及token前缀),严禁打印原始token。

JWT鉴权失败时为什么默认没日志
Echo 的 jwt.FromAuthHeader() 中间件在解析失败或签名无效时,直接返回 401 响应并中断请求链,不触发任何日志逻辑。这不是 Bug,而是设计使然——它把错误处理交还给上层,避免中间件越权打日志(比如敏感的 token 内容被意外记录)。
用自定义 JWT 中间件捕获并记录失败
最直接的方式是封装一层自己的中间件,在调用 jwt.JWT() 前后加日志钩子。关键点在于:必须用 echo.HTTPError 类型断言来区分鉴权失败和其他错误,且只对 401 做日志,避免把 404、500 等也混进来。
示例代码片段:
func LoggingJWTMiddleware(secret string) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
err := jwt.JWT([]byte(secret))(next)(c)
if he, ok := err.(*echo.HTTPError); ok && he.Code == http.StatusUnauthorized {
// 只记录 401,且排除空 token 或格式错误等常见干扰
tokenStr := c.Request().Header.Get("Authorization")
if len(tokenStr) > 7 && strings.HasPrefix(tokenStr, "Bearer ") {
log.Printf("[JWT AUTH FAIL] %s %s %v", c.Request().RemoteAddr, c.Request().URL.Path, he.Message)
}
}
return err
}
}
}
- 不要直接打印完整
tokenStr,避免泄露凭证;只记录前缀和长度判断即可 - 务必检查
Authorization头是否存在且为 Bearer 格式,否则会把缺失 header 的请求也记为“鉴权失败” - 如果用了
jwt.WithConfig()自定义配置,需确保ErrorHandler未覆盖默认行为,否则你的中间件可能收不到*echo.HTTPError
配合 Echo 的全局错误处理器统一处理
如果你已在用 e.HTTPErrorHandler,更推荐在这里做日志,避免中间件嵌套混乱。注意:JWT 中间件抛出的错误会被这个 handler 拦截,但你需要识别来源。
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
做法是给 JWT 错误加一个可识别的标记,比如自定义 error 包装:
type JWTError struct {
Err error
}
func (e *JWTError) Error() string { return "jwt auth failed: " + e.Err.Error() }
// 在自定义 JWT 中间件里:
return &JWTError{Err: err}
然后在全局 handler 中判断:
e.HTTPErrorHandler = func(err error, c echo.Context) {
if _, ok := err.(*JWTError); ok {
log.Printf("[JWT AUTH FAIL] %s %s", c.Request().RemoteAddr, c.Request().URL.Path)
}
c.Logger().Error(err)
c.JSON(getHTTPErrorCode(err), map[string]string{"message": "Unauthorized"})
}
- 这种写法能和其它中间件(如 RBAC、IP 白名单)的日志逻辑保持一致
- 注意
getHTTPErrorCode()需自行实现,因为*JWTError不带状态码,要靠类型判断映射到 401 - 别忘了在 logger 输出前调用
c.SetParamNames()等清理操作,否则某些上下文字段可能为空
生产环境要注意 token 泄露和日志脱敏
线上绝对不能记录原始 token 字符串,哪怕只是调试用。JWT 的 payload 部分虽是 base64 编码,但可轻易解码,里面常含用户 ID、角色、过期时间等敏感信息。
- 记录时只保留 token 哈希前 8 位:
fmt.Sprintf("%x", md5.Sum([]byte(tokenStr))[:4]) - 避免在日志中出现
sub、uid、email等 claim 字段名,即使你从解析后的*jwt.Token中取值 - 如果用 ELK 或 Loki 做日志聚合,建议在采集端加过滤规则,自动 scrub 掉所有匹配
Bearer [a-zA-Z0-9_\-\.]{20,}的字段
真正难的不是记下“鉴权失败”,而是记清楚谁、在什么上下文、因哪类原因失败,又不留下安全隐患。日志字段设计比加一行 log.Printf 花的时间多十倍。










