基于Echo与oapi-codegen的JWT中间件重复返回401响应问题
Echo框架JWT中间件与oapi-codegen冲突导致双重401响应的解决方法
问题现象
在基于Go Echo框架开发的应用中,集成echo-jwt中间件做JWT身份验证,同时用oapi-codegen生成的StrictHandler做OpenAPI校验。请求受保护路由时,会收到两个401响应:
{"code": 401,"message": "invalid token"} {"code": 401,"message": "missing or malformed JWT"}
第一个响应来自JWT中间件,第二个来自路由处理器,说明中间件在令牌无效/缺失时未正确终止请求,导致后续路由逻辑仍被执行。
核心原因
- 错误处理不符合Echo框架规范:自定义
ErrorHandler中直接调用c.JSON返回响应,但未返回echo.HTTPError类型错误。Echo框架仅在中间件返回非nil错误时才会终止请求流程,直接写响应不会触发中断,后续路由处理器仍会执行。 - SuccessHandler误用:
SuccessHandler仅用于验证成功后的逻辑扩展,不能在其中通过c.JSON终止请求。即使调用c.Response().Flush(),也无法阻止Echo继续执行后续路由处理器。
修复方案
1. 修正JWT中间件的ErrorHandler
让ErrorHandler返回echo.NewHTTPError,而非直接调用c.JSON。Echo框架会捕获这个错误并终止请求流程,不会再执行后续路由。
修改后的JWT中间件ErrorHandler代码:
ErrorHandler: func(c echo.Context, err error) error { var message string if err == echojwt.ErrJWTMissing { message = "missing or malformed JWT" } else { message = "invalid token" } // 返回echo HTTPError,触发框架终止请求流程 return echo.NewHTTPError(http.StatusUnauthorized, message) },
2. 重构令牌黑名单校验逻辑
将SuccessHandler中的黑名单校验移到独立中间件或整合到JWT验证逻辑中,通过返回错误终止请求。
方案一:独立中间件处理黑名单校验
新增黑名单校验中间件:
// BlacklistCheckMiddleware 校验令牌是否在黑名单中 func (m *JWTMiddleware) BlacklistCheckMiddleware() echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { userToken, ok := c.Get("user").(*jwt.Token) if !ok { return echo.NewHTTPError(http.StatusUnauthorized, "invalid token") } tokenString := userToken.Raw isBlacklisted, err := m.TokenBlacklist.IsBlacklisted(tokenString) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "internal server error") } if isBlacklisted { return echo.NewHTTPError(http.StatusUnauthorized, "invalid token") } // 校验通过,继续执行后续逻辑 return next(c) } } }
注册路由时将该中间件加入受保护组:
protected := e.Group("/auth") protected.Use(jwtMiddleware.Handler()) protected.Use(jwtMiddleware.BlacklistCheckMiddleware()) // 新增黑名单校验中间件
方案二:整合到JWT中间件的TokenValidator(紧凑版)
在JWT配置中自定义TokenValidator,将JWT签名验证和黑名单校验合并:
config := echojwt.Config{ SigningKey: []byte(m.SecretKey), ErrorHandler: func(c echo.Context, err error) error { var message string if err == echojwt.ErrJWTMissing { message = "missing or malformed JWT" } else { message = "invalid token" } return echo.NewHTTPError(http.StatusUnauthorized, message) }, TokenValidator: func(c echo.Context, token *jwt.Token) error { // 验证JWT签名 if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return echo.NewHTTPError(http.StatusUnauthorized, "invalid signing method") } // 校验令牌是否在黑名单中 tokenString := token.Raw isBlacklisted, err := m.TokenBlacklist.IsBlacklisted(tokenString) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "internal server error") } if isBlacklisted { return echo.NewHTTPError(http.StatusUnauthorized, "invalid token") } return nil }, ContinueOnIgnoredError: false, }
此方案无需单独的SuccessHandler和黑名单中间件,所有校验逻辑在TokenValidator中完成,验证失败直接返回错误终止请求。
3. 优化自定义HTTP错误处理器
修改customHTTPErrorHandler,避免重复写入响应:
import "fmt" func customHTTPErrorHandler(err error, c echo.Context) { // 如果响应已发送,直接返回 if c.Response().Committed { return } code := http.StatusInternalServerError message := "internal server error" if he, ok := err.(*echo.HTTPError); ok { code = he.Code if he.Message != nil { message = fmt.Sprintf("%v", he.Message) } } c.JSON(code, map[string]string{ "message": message, }) }
验证效果
修改完成后,请求受保护路由时,中间件验证失败会返回单个401响应,请求流程被终止,路由处理器不会再执行。
内容的提问来源于stack exchange,提问作者Coderlife
相关产品推荐
相关产品推荐

