在Go中需要HTTP Basic Auth的惯用法?

Ral*_*alf 35 go

情况:

我正在使用Gorilla的mux作为路由器构建REST API.

我想知道如何使用简单的HTTP Basic Auth保护特定路由.我不需要从文件或任何外部源读取凭据,我真的只想通过硬编码的HTTP Basic Auth用户名和密码来保护选定的路由.

题:

在Go中这样做的惯用方法是什么?Gorilla是否提供任何东西以使其更容易?如果你能提供几行代码,那就太棒了.

Tim*_*mmm 35

将几个答案组合成一个简单的复制/粘贴:

// BasicAuth wraps a handler requiring HTTP basic auth for it using the given
// username and password and the specified realm, which shouldn't contain quotes.
//
// Most web browser display a dialog with something like:
//
//    The website says: "<realm>"
//
// Which is really stupid so you may want to set the realm to a message rather than
// an actual realm.
func BasicAuth(handler http.HandlerFunc, username, password, realm string) http.HandlerFunc {

    return func(w http.ResponseWriter, r *http.Request) {

        user, pass, ok := r.BasicAuth()

        if !ok || subtle.ConstantTimeCompare([]byte(user), []byte(username)) != 1 || subtle.ConstantTimeCompare([]byte(pass), []byte(password)) != 1 {
            w.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`"`)
            w.WriteHeader(401)
            w.Write([]byte("Unauthorised.\n"))
            return
        }

        handler(w, r)
    }
}

...

http.HandleFunc("/", BasicAuth(handleIndex, "admin", "123456", "Please enter your username and password for this site"))
Run Code Online (Sandbox Code Playgroud)

请注意,subtle.ConstantTimeCompare()仍取决于长度,因此如果您这样做,攻击者可能会计算出用户名和密码的长度.要绕过它,你可以哈希它们或添加一个固定的延迟.

  • 如果你不使用`ConstantTimeCompare()`,人们可以计算出操作花了多长时间.从那以后,他们可以计算出多少密码匹配并尝试所有字母,直到密码的第一个字母匹配,然后是第二个字母,依此类推.如果你在比较之前对密码进行散列(你真的应该这样做 - 密码不应该以纯文本形式存储)那么这不是一个问题,但无论如何可能仍然是一个好主意.如果您不这样做,您将永远不会发现任何问题,但这是一个潜在的安全漏洞. (6认同)
  • @Richard"我想不出这怎么可能发生,所以它可能不会." 正是那种导致安全漏洞的思维方式.[定时攻击可以通过网络完成](https://security.stackexchange.com/a/183826/143186).在这种情况下,我认为它可能*不可能,但我希望这也是OpenSSH开发人员所想的. (3认同)
  • 很确定这是无稽之谈,至少对于面向公共互联网的应用程序和基本身份验证而言......尝试根据系统之间的所有延迟来计算 3 位数和 4 位数密码之间的差异。汇编器中的单次比较需要 1-3 个时钟周期,具体取决于寄存器或内存比较...以及 CPU/RAM 缓存。将代码冒泡通过几层 C 和/或本机 go 以及网络延迟……您必须提供证据。更不用说在上面的例子中它是明文比较。哦,我们还可以说,黑客不知道是否有数据库或缓存。 (2认同)

vou*_*rus 23

检查req.BasicAuth() https://golang.org/pkg/net/http/#Request.BasicAuth

你可以在你的处理程序中检查这个,或像这样包装你的处理程序

func auth(fn http.HandlerFunc) http.HandlerFunc {
  return func(w http.ResponseWriter, r *http.Request) {
    user, pass, _ := r.BasicAuth()
    if !check(user, pass) {
       http.Error(w, "Unauthorized.", 401)
       return
    }
    fn(w, r)
  }
}
Run Code Online (Sandbox Code Playgroud)

哪里

check(u, p string) bool 
Run Code Online (Sandbox Code Playgroud)

是您必须根据存储凭据的方式自行编写的函数.现在你可以使用:

auth(originalHandler)
Run Code Online (Sandbox Code Playgroud)

无论你以前在哪里通过originalHandler.

[编辑:值得补充的是,你的检查功能应该能够抵抗定时攻击等侧通道攻击.存储的密码也应使用加密随机盐进行哈希处理.此外,您应该使用OAuth,让已建立的身份提供商担心您的密码安全.]

  • `http.Error(w,"Unauthorized.",http.StatusUnauthorized)` (7认同)

nem*_*emo 19

截至2016年,我建议使用此答案.在任何情况下,请将您的HTTP基本身份验证包装在SSL中,以避免将用户名和密码作为纯文本发送.


只需将处理程序包装在另一个处理程序中,并在传入请求中使用问题WWW-Authorization标头.

示例(完整版):

func checkAuth(w http.ResponseWriter, r *http.Request) bool {
    s := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
    if len(s) != 2 { return false }

    b, err := base64.StdEncoding.DecodeString(s[1])
    if err != nil { return false }

    pair := strings.SplitN(string(b), ":", 2)
    if len(pair) != 2 { return false }

    return pair[0] == "user" && pair[1] == "pass"
}

yourRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    if checkAuth(w, r) {
        yourOriginalHandler.ServeHTTP(w, r)
        return
    }

    w.Header().Set("WWW-Authenticate", `Basic realm="MY REALM"`)
    w.WriteHeader(401)
    w.Write([]byte("401 Unauthorized\n"))
})
Run Code Online (Sandbox Code Playgroud)

不幸的是,标准.库只提供客户端基本身份验证,因此您必须自己动手或使用库,例如库.


Mat*_*att 5

请求net/http类型具有用于执行此操作的辅助函数(在 go 1.7 上测试)。尼莫的答案的一个简单版本如下所示:

func basicAuthHandler(user, pass, realm string, next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        if checkBasicAuth(r, user, pass) {
            next(w, r)
            return
        }

        w.Header().Set("WWW-Authenticate", fmt.Sprintf(`Basic realm="%s"`, realm))
        w.WriteHeader(401)
        w.Write([]byte("401 Unauthorized\n"))
    }
}

func checkBasicAuth(r *http.Request, user, pass string) bool {
    u, p, ok := r.BasicAuth()
    if !ok {
        return false
    }
    return u == user && p == pass
}
Run Code Online (Sandbox Code Playgroud)

然后只需使用业务逻辑创建处理程序并将其作为next参数传递basicAuthHandler以创建新的“包装”handlerFunc。