dongliang1654 2018-07-07 14:46
浏览 58
已采纳

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

Two approaches of authentication have been used a lot in many books and blogs for Go:

Use 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
}

Then handler functions call the getCurrentUser to get the user for each request. It's possible to use a function decorator that wraps around other handlers and check the authentication before other handler functions get executed.

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
    }
}

Using 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)) 
    }
}

Then for each handler (like secretInfoHandler), instead of calling getCurrentUser(r *http.Request), I just need to check if the Context that comes with the http.Request contains certain authenticaiton info.

They appear to be equivalent. So, what are the technical advantages/disadvantages of each approach? If they are indeed equivalent, which one is better for real-world production code to use?

  • 写回答

2条回答 默认 最新

  • dreamwind1985 2018-07-07 17:52
    关注

    I think your examples are a little confused. The seem to contain a combination of authorising in handlers, and authorising in middleware wrapping handlers.

    Authorisation in handlers (using getCurrentUser)

    This is the first example, calling some getCurrentUser(request) function from the request handler.

    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
    }
    

    ^^ this is taken from your example, but you've also included the MustAuthenticate middleware which I think is irrelevant here.

    Authorisation as middleware (using context)

    This is your second example, using the context.Context's key-value bucket as a means of sending values down into the handler.

    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)) 
        }
    }
    

    Comparison

    It's important to note from the start that; while I've split these two in to auth in handler and auth in middleware, each with their own using. There's no reason why you couldn't swap way that the authorisation happens. e.g. Authorisation in middleware using getCurrentUser(request), whether you should is discussed below.

    Breaking this down, the question you really need to ask is:

    "Where do you need access to your user struct?"

    This will help you to decide which to use.

    In general it's perfectly valid to put request scoped variables in context.Context. Variables such as tracing information regularly go in to the context. The main problem with context is that it's not compile time checked, and not type safe. You are making the assumption in later code that your user object has been set in the context, and this makes your code coupled in a non-obvious way.

    The benefits of context.Context method is that if your user object needs to descend multiple levels of function calls, you don't need to wire it through your entire code base. You can just grab it out of the context later. It's a values bucket, allowing for greater flexibility in the code later.

    With the authorisation in handlers method; you could potentially have the same authorisation and error handling code in many handlers. If you try and extract this to a middlware; you will quickly find that there's no nice way of maintaining the http.Handler interface and passing the user object extracted in the middlware. (which is exactly why, in the example above, the user object is put inside the request object).

    The benefits of authorisation in the controller using getCurrentUser(request) is that it's obvious where the user struct is created, it's clear what authorisation has happened looking at that handler, and there's no way that user object could not be present (assuming no error is returned).

    TL;DR

    Depends on where you need you user object; and how many handlers there are.

    1. Very few handlers, no need for user outside handler: getCurrentUser(req) inside handler
    2. Many handlers, no need for user outside authorisation: getCurrentUser(req) in middlware, and don't put it in the context (as you don't need it later)
    3. Many handlers, user is required later in code: Authorisation in middleware, put the user in the context, and get it out later.
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥15 基于FOC驱动器,如何实现卡丁车下坡无阻力的遛坡的效果
  • ¥15 IAR程序莫名变量多重定义
  • ¥15 (标签-UDP|关键词-client)
  • ¥15 关于库卡officelite无法与虚拟机通讯的问题
  • ¥100 已有python代码,要求做成可执行程序,程序设计内容不多
  • ¥15 目标检测项目无法读取视频
  • ¥15 GEO datasets中基因芯片数据仅仅提供了normalized signal如何进行差异分析
  • ¥100 求采集电商背景音乐的方法
  • ¥15 数学建模竞赛求指导帮助
  • ¥15 STM32控制MAX7219问题求解答