You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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中间件,第二个来自路由处理器,说明中间件在令牌无效/缺失时未正确终止请求,导致后续路由逻辑仍被执行。

核心原因

  1. 错误处理不符合Echo框架规范:自定义ErrorHandler中直接调用c.JSON返回响应,但未返回echo.HTTPError类型错误。Echo框架仅在中间件返回非nil错误时才会终止请求流程,直接写响应不会触发中断,后续路由处理器仍会执行。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 19:20:58