使用请求和上下文进行身份验证

时间:2018-07-07 14:46:14

标签: authentication go design-patterns

Go的许多书籍和博客中已广泛使用两种身份验证方法:

使用http.Request

func getCurrentUser(r *http.Request) (*User, error) {
    // get JWT token or cookie and find corresponding sessions 
    // and account, then return the user and nil error;
    // if user is not found, return a nil user and a non-nil error
}

然后处理程序函数调用getCurrentUser以获取每个用户 请求。可以使用环绕的函数装饰器 其他处理程序,并在其他处理程序功能之前检查身份验证 被执行。

func secretInfoHandler (w http.ResponseWriter, r * http.Request) {
     user, err := getCurrentUser(r)
     if err != nil {
          // write http.Unauthorized, and return
     }
     // otherwise, process request return data
}
func MustAuthenticate (h http.HandlerFunc) http.HandlerFunc {
    return func (w http.ResponseWriter, r *http.Request) {
          // chekc authentication, if pass:
          h.ServeHTTP(w, r)
          // if fail:
          w.WriteHeader(http.StatusUnauthorized)
          return
    }
}

使用context.Context

// use the same getCurrentUser() as the one above
func MustAuthenticate (h http.HandlerFunc) http.HandlerFunc {
    return func (w http.ResponseWriter, r *http.Request) {
        user, err := getCurrentUser(r)
        if err != nil {
            // write error code then return
        }
        ctx := context.WithValue(r.Context(), someKey, someValue)
        h(w, r.WithContext(ctx)) 
    }
}

然后针对每个处理程序(例如secretInfoHandler),而不是调用getCurrentUser(r *http.Request),我只需要检查Context随附的http.Request是否包含某些身份验证信息

它们似乎是等效的。那么,每种方法的技术优势/劣势是什么?如果它们确实等效,那么使用哪种代码更适合实际的生产代码?

2 个答案:

答案 0 :(得分:1)

我认为您的示例有些混乱。似乎包含处理程序中的授权和中间件包装处理程序中的授权的组合。

处理程序中的授权(使用getCurrentUser)

这是第一个示例,从请求处理程序中调用一些getCurrentUser(request)函数。

func secretInfoHandler (w http.ResponseWriter, r * http.Request) {
     user, err := getCurrentUser(r)
     if err != nil {
          // write http.Unauthorized, and return
     }
     // otherwise, process request return data
}

^^这是从您的示例中获取的,但是您还包括了MustAuthenticate中间件,我认为这里与它无关。

授权为中间件(使用上下文)

这是您的第二个示例,使用context.Context的键值存储区作为将值向下发送到处理程序的一种方法。

func MustAuthenticate (h http.HandlerFunc) http.HandlerFunc {
    return func (w http.ResponseWriter, r *http.Request) {
        user, err := getCurrentUser(r)
        if err != nil {
            // write error code then return
        }
        ctx := context.WithValue(r.Context(), someKey, someValue)
        h.ServeHTTP(w, r.WithContext(ctx)) 
    }
}

比较

从一开始就必须注意;虽然我将这两者分为处理程序中的auth和中间件中的auth,每个都有自己的 using 。没有理由不能交换授权发生的方式。例如下面讨论是否使用getCurrentUser(request)在中间件中进行授权。

简而言之,您真正要问的问题是:

“您需要在哪里访问用户结构?”

这将帮助您决定使用哪个。

通常,将请求范围的变量放在context.Context中是完全有效的。诸如跟踪信息之类的变量定期进入上下文。上下文的主要问题是它没有检查编译时间,也不安全输入。您将在以后的代码中假设已在上下文中设置了用户对象,这使您的代码以一种非显而易见的方式耦合。

context.Context方法的好处是,如果您的用户对象需要下降多个级别的函数调用,则无需在整个代码库中进行连接。您可以稍后将其移出上下文。这是一个值存储桶,以后可以在代码中提供更大的灵活性。

具有处理程序方法中的授权;您可能在许多处理程序中具有相同的授权和错误处理代码。如果尝试将其提取到中间件中;您会很快发现,没有很好的方法来维护http.Handler接口并传递从中间件中提取的用户对象。 (这就是在上面的示例中将用户对象放在请求对象内的原因)。

使用getCurrentUser(request)在控制器中进行授权的好处是,很明显在哪里创建了用户结构,很明显查看该处理程序时发生了什么授权,并且无​​法显示用户对象(假设没有错误返回)。

TL; DR

取决于用户对象的位置;以及有多少个处理程序。

  1. 很少有处理程序,不需要外部处理程序的用户getCurrentUser(req)位于处理程序内部
  2. 许多处理程序,不需要中间授权中的用户getCurrentUser(req),也不要将其放在上下文中(因为以后不需要)
  3. >
  4. 许多处理程序,稍后在代码中需要用户:在中间件中进行授权,将用户置于上下文中,然后将其发布。

答案 1 :(得分:0)

您已经在请求中拥有了身份验证所需的所有信息。上下文也是请求属性。提供具有auth上下文的请求,您不会提供任何新信息,而只是使此信息易于接受。 从文档

  

仅对传递的请求范围的数据使用上下文值   流程和API,而不是用于将可选参数传递给函数。