如何使用中间件以惯用方式记录身份验证信息

时间:2017-05-11 12:14:24

标签: http logging go middleware

假设我正在构建一个Go Web应用程序,具有以下要求:

  • 可能发出HTTP响应的Auth中间件(如果出错)
  • 记录中间件应记录正常的请求信息(请求URL,响应状态,响应大小等)以及身份验证信息(即经过身份验证的用户名)
  • context.Context
  • 的惯用法

乍一看,这似乎很容易:

r.Use(authMiddleware)
r.Use(loggingMiddleware)
// Other middlewares/routes

但是如果authMiddleware发出400,401,403或类似的错误,则会失败,因为这样就不会调用日志记录中间件。

所以重新订购似乎是合适的:

r.Use(loggingMiddleware)
r.Use(authMiddleware)
// Other middlewares/routes

但现在,假设我使用context.WithValue()设置了身份验证信息,则记录器不知道身份验证信息。它仍然可以记录HTTP响应代码,大小等,但不记录经过身份验证的用户等。

这导致了一种非常复杂的解决方案:

r.Use(injectAuthPlaceholder)
r.Use(loggingMiddleware)
r.Use(authMiddleware)

injectAuthPlaceholder的作用类似于:

var user *string
ctx = context.WithValue(ctx, userKey, user)

然后authMiddleware为用户设置:

userPtr = ctx.Value(userKey).(*string)
*userPtr = authedUsername

这具有让记录器访问经过身份验证的用户名的效果,但它需要一个由两部分组成的身份验证机制,它似乎违反惯用法context.Context,并且感觉很糟糕。

对于这种鸡蛋问题,有什么更惯用的解决方案?

1 个答案:

答案 0 :(得分:0)

  

我记录的请求,包括身份验证信息,以及其他相关信息(请求的URL,响应代码,响应大小等)

根据我对你在这两个中间件中所使用的逻辑的理解,我感觉日志记录中间件混合了问题。我认为HTTP请求信息日志应该与身份验证日志分开。

正如我所说,日志记录是关于记录您的应用中与您相关的事件。此外,日志需要上下文相关,因此需要对他们记录的内容有一个深入的了解。

  

配置auth涉及提供用户数据库,加密方法,机密等。配置日志记录涉及提供日志记录格式和日志记录目标(例如文件或远程服务器)。这些之间有0重叠。它们的相关方式与HTTP请求与日志记录相关。 如果合并日志记录和auth中间件是有意义的,那么合并日志记录和请求处理是有意义的。还有其他一切。

是的,中间件有点像去包。就像标准的go包一样,我不会基于它们的抽象层来拆分它们,而是基于它们解决的域的部分。因此,如果我有一个包auth,我会在其中添加所有身份验证逻辑,从域模型到传输层(例如http)。我强烈建议您Marcus Olsson excellent talk关于DDD应用于Go。

中间件示例

func mwLogging(next MiddlewareFunc) MiddlewareFunc {
    return func(c *Context) {
        c.Ctx.Trace("h.http.req.start", "Request start",
            log.String("method", c.Method),
            log.String("path", c.Path),
            log.String("user_agent", c.Req.Header.Get("User-Agent")),
        )

        next(c)

        c.Ctx.Trace("h.http.req.end", "Request end",
            log.Int("status", c.Res.Code()),
            log.Duration("duration", time.Since(c.StartTime)),
        )
    }
}

func mwAuth(next MiddlewareFunc) MiddlewareFunc {
    return func(c *http.Context) {
        // Just an example
        session, err := Auth(c.Req.Header.Get("Authorization"))
        if err != nil {
            c.Ctx.Warning("http.auth.error", "Authentication failed", log.Error(err))
            c.Res.WriteHeader(http.StatusUnauthorized)
            return
        }

        // You could store the session in context here                
        context.WithValue(ctx, "session", session)
        next(c)
    }
}

无耻插件:logging middleware取自stairlin/lego