Idiomatic way of requiring HTTP Basic Auth in Go?

huangapple go评论91阅读模式
英文:

Idiomatic way of requiring HTTP Basic Auth in Go?

问题

情况:

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

我想知道如何使用简单的HTTP基本身份验证来保护特定的路由。我不需要从文件或任何外部来源读取凭据,我只是想通过硬编码的HTTP基本身份验证用户名和密码来保护选定的路由。

问题:

在Go中,有什么惯用的方法可以实现这个?Gorilla是否提供了任何简化的方法?如果你能提供几行代码,那就太好了。

英文:

Situation:

I'm building a REST API using Gorilla's mux as the router.

I'm wondering how I can protect specific routes with simple HTTP Basic Auth. I don't have a need to read the credentials from a file or any external source, I really just want to protect selected routes by a hard coded HTTP Basic Auth username and password.

Question:

What is the idiomatic way of doing so in Go? Does Gorilla offer anything to make it more easy? If you could provide a few lines of code, that would be just wonderful.

答案1

得分: 52

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

// BasicAuth使用给定的用户名、密码和指定的领域对需要HTTP基本身份验证的处理程序进行包装,
// 领域不应包含引号。
//
// 大多数Web浏览器会显示类似以下内容的对话框:
//
//    网站说:“<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("未经授权。\n"))
			return
		}

		handler(w, r)
	}
}

...

http.HandleFunc("/", BasicAuth(handleIndex, "admin", "123456", "请输入您在此站点的用户名和密码"))

请注意,subtle.ConstantTimeCompare()仍然取决于长度,因此攻击者可能可以通过这种方式确定用户名和密码的长度。为了解决这个问题,您可以对它们进行哈希处理或添加固定延迟。

英文:

Combining a couple of answers into an easy copy/paste:

// BasicAuth wraps a handler requiring HTTP basic auth for it using the given
// username and password and the specified realm, which shouldn&#39;t contain quotes.
//
// Most web browser display a dialog with something like:
//
//    The website says: &quot;&lt;realm&gt;&quot;
//
// 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(&quot;WWW-Authenticate&quot;, `Basic realm=&quot;`+realm+`&quot;`)
			w.WriteHeader(401)
			w.Write([]byte(&quot;Unauthorised.\n&quot;))
			return
		}

		handler(w, r)
	}
}

...

http.HandleFunc(&quot;/&quot;, BasicAuth(handleIndex, &quot;admin&quot;, &quot;123456&quot;, &quot;Please enter your username and password for this site&quot;))

Note that subtle.ConstantTimeCompare() still depends on the length, so it is probably possible for attackers to work out the length of the username and password if you do it like this. To get around that you could hash them or add a fixed delay.

答案2

得分: 25

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

其中

check(u, p string) bool

是一个你需要根据你存储凭据的方式自己编写的函数。现在你可以使用:

auth(originalHandler)

替换之前传递 originalHandler 的地方。

[编辑:值得注意的是,你的 check 函数应该抵御诸如时序攻击之类的侧信道攻击。此外,存储的密码应该使用加密随机盐进行哈希处理。另外,你可能应该使用 OAuth,并让已建立的身份提供者为你处理密码安全性。]

英文:

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

You can check this in your handler or wrap your handler like so:

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, &quot;Unauthorized.&quot;, 401)
       return
    }
    fn(w, r)
  }
}

Where

check(u, p string) bool 

is a function you will have to write yourself based on how you are storing credentials. Now you can use:

auth(originalHandler)

Wherever you were passing originalHandler before.

[edit: It's worth adding that your check function should be resistant to side channel attacks like timing attacks. Also stored passwords should be hashed with a cryptographically random salt. Also you should probably use OAuth instead and let an established identity provider worry about password security for you.]

答案3

得分: 20

截至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"))
})

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

英文:

As of 2016, I would suggest to use this answer. In any case, wrap your HTTP basic auth in SSL to avoid sending username and password as plain text.


Just wrap your handler in another handler and use issue WWW-Authorization header on the incoming request.

Example (full version):

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

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

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

	return pair[0] == &quot;user&quot; &amp;&amp; pair[1] == &quot;pass&quot;
}

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

	w.Header().Set(&quot;WWW-Authenticate&quot;, `Basic realm=&quot;MY REALM&quot;`)
	w.WriteHeader(401)
	w.Write([]byte(&quot;401 Unauthorized\n&quot;))
})

Unfortunately, the std. library only offers client basic auth and therefore you have to
do it yourself or use a library, for example this one.

答案4

得分: 5

net/http请求类型有一些辅助函数可以实现这个功能(在go 1.7上进行了测试)。一个简化版本的nemo的答案如下所示:

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
}

然后,只需创建具有业务逻辑的处理程序,并将其作为basicAuthHandler中的next参数传递,以创建一个新的“包装”handlerFunc

英文:

The net/http request type has helper functions for doing this (tested on go 1.7). A simpler version of nemo's answer would look like such:

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(&quot;WWW-Authenticate&quot;, fmt.Sprintf(`Basic realm=&quot;%s&quot;`, realm))
		w.WriteHeader(401)
		w.Write([]byte(&quot;401 Unauthorized\n&quot;))
	}
}

func checkBasicAuth(r *http.Request, user, pass string) bool {
	u, p, ok := r.BasicAuth()
	if !ok {
		return false
	}
	return u == user &amp;&amp; p == pass
}

then simply create your handler with business logic and pass it as the next argument in basicAuthHandler to create a new "wrapped" handlerFunc.

答案5

得分: 2

你可以使用go-http-auth来完成这个任务。如果你正在使用net/http,它会很适合你的需求。

英文:

go-http-auth will do it for you. It will fit right in if you're using net/http.

答案6

得分: 1

我意识到我来晚了。我刚好在重新研究HTTP基本身份验证。在阅读了这里的所有答案后,我实际上采用了Timmmm解决方案的一个变体。我甚至进一步添加了哈希处理,以提高安全性,根据他的建议。我想我不妨分享一下我修改后的代码。

userhash := hasher("admin")
passhash := hasher("$CrazyUnforgettablePassword?")
realm := "请输入用户名和密码"

http.HandleFunc("/", authHandler(indexHandler, userhash, passhash, realm))

// 上述代码显然应该与http监听器等一起放在main()函数中。

// hasher使用包"crypto/sha256"
func hasher(s string) []byte {
    val := sha256.Sum256([]byte(s))
    return val[:]
}

func authHandler(handler http.HandlerFunc, userhash, passhash []byte, realm string) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        user, pass, ok := r.BasicAuth()
        if !ok || subtle.ConstantTimeCompare(hasher(user),
            userhash) != 1 || subtle.ConstantTimeCompare(hasher(pass), passhash) != 1 {
            w.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`"`)
            http.Error(w, "未经授权。", http.StatusUnauthorized)
            return
        }
        handler(w, r)
    }
}
英文:

I realize I'm late to the party here. I just happened to be revisiting HTTP Basic Authentication. After going through all the answers here I actually settled on a variation of Timmmm's solution. I even took it the step farther and added the hashing as per his suggestion to improve security. Figured I might as well share my variation of the code.

userhash := hasher(&quot;admin&quot;)
passhash := hasher(&quot;$CrazyUnforgettablePassword?&quot;)
realm := &quot;Please enter username and password&quot;

http.HandleFunc(&quot;/&quot;, authHandler(indexHandler, userhash, passhash, realm))

// Above code should obviously be in main() along with the http listener, etc.

// hasher uses package &quot;crypto/sha256&quot;
func hasher(s string) []byte {
	val := sha256.Sum256([]byte(s))
	return val[:]
}

func authHandler(handler http.HandlerFunc, userhash, passhash []byte, realm string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		user, pass, ok := r.BasicAuth()
		if !ok || subtle.ConstantTimeCompare(hasher(user),
			userhash) != 1 || subtle.ConstantTimeCompare(hasher(pass), passhash) != 1 {
			w.Header().Set(&quot;WWW-Authenticate&quot;, `Basic realm=&quot;`+realm+`&quot;`)
			http.Error(w, &quot;Unauthorized.&quot;, http.StatusUnauthorized)
			return
		}
		handler(w, r)
	}
}

huangapple
  • 本文由 发表于 2014年2月21日 21:52:48
  • 转载请务必保留本文链接:https://go.coder-hub.com/21936332.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定