如何在Golang中使用摘要认证进行HTTP POST?

8

我正在尝试使用需要摘要认证的Gerrit API。经过一些阅读,我知道我应该发送一个请求,得到一个401错误,然后使用领域和随机数以及可能的其他头信息来使用MD5创建实际请求认证。我找到了一些关于摘要的示例,但它们似乎都是服务器端的,而不是客户端的。


我发出的每个请求都只返回401。我已经找到了如何发出请求并将立即添加答案。 - mvndaai
1
这里有一个可以简化操作的包:https://github.com/icholy/digest - Ilia Choly
2个回答

16

我主要是按照维基百科上关于如何发出请求的说明,然后查看详细的curl -v --digest --user username:password http://url.com/api请求以确定其中的部分。这里是关键部分:您需要发出请求,收到401未授权响应,然后使用未经授权请求标头中的noncerealm基于MD5摘要计算授权标头。

import (
    "bytes"
    "crypto/md5"
    "crypto/rand"
    "encoding/hex"
    "fmt"
    "io"
    "io/ioutil"
    "log"
    "net/http"
    "strings"
)

func digestPost(host string, uri string, postBody []byte) bool {
    url := host + uri
    method := "POST"
    req, err := http.NewRequest(method, url, nil)
    req.Header.Set("Content-Type", "application/json")
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    if resp.StatusCode != http.StatusUnauthorized {
        log.Printf("Recieved status code '%v' auth skipped", resp.StatusCode)
        return true
    }
    digestParts := digestParts(resp)
    digestParts["uri"] = uri
    digestParts["method"] = method
    digestParts["username"] = "username"
    digestParts["password"] = "password"
    req, err = http.NewRequest(method, url, bytes.NewBuffer(postBody))
    req.Header.Set("Authorization", getDigestAuthrization(digestParts))
    req.Header.Set("Content-Type", "application/json")

    resp, err = client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()
    if resp.StatusCode != http.StatusOK {
        body, err := ioutil.ReadAll(resp.Body)
        if err != nil {
            panic(err)
        }
        log.Println("response body: ", string(body))
        return false
    }
    return true
}

func digestParts(resp *http.Response) map[string]string {
    result := map[string]string{}
    if len(resp.Header["Www-Authenticate"]) > 0 {
        wantedHeaders := []string{"nonce", "realm", "qop"}
        responseHeaders := strings.Split(resp.Header["Www-Authenticate"][0], ",")
        for _, r := range responseHeaders {
            for _, w := range wantedHeaders {
                if strings.Contains(r, w) {
                    result[w] = strings.Split(r, `"`)[1]
                }
            }
        }
    }
    return result
}

func getMD5(text string) string {
    hasher := md5.New()
    hasher.Write([]byte(text))
    return hex.EncodeToString(hasher.Sum(nil))
}

func getCnonce() string {
    b := make([]byte, 8)
    io.ReadFull(rand.Reader, b)
    return fmt.Sprintf("%x", b)[:16]
}

func getDigestAuthrization(digestParts map[string]string) string {
    d := digestParts
    ha1 := getMD5(d["username"] + ":" + d["realm"] + ":" + d["password"])
    ha2 := getMD5(d["method"] + ":" + d["uri"])
    nonceCount := 00000001
    cnonce := getCnonce()
    response := getMD5(fmt.Sprintf("%s:%s:%v:%s:%s:%s", ha1, d["nonce"], nonceCount, cnonce, d["qop"], ha2))
    authorization := fmt.Sprintf(`Digest username="%s", realm="%s", nonce="%s", uri="%s", cnonce="%s", nc="%v", qop="%s", response="%s"`,
        d["username"], d["realm"], d["nonce"], d["uri"], cnonce, nonceCount, d["qop"], response)
    return authorization
}

你的意思是:“在未经授权的响应头中”,对吗? - Joppe
你会收到需要用来发起请求的部分。然后你必须使用它们来发起请求,但这不仅仅是复制和粘贴。 - mvndaai
1
导入 ( "bytes" "crypto/md5" "crypto/rand" "encoding/hex" "fmt" "io" "io/ioutil" "log" "net/http" "strings" ) - mbigras
@mvndaai,谢谢你提供这份源代码!我想知道你是否为此编写了测试? - gogofan
@JeffreyYong,很抱歉,我已经没有这个项目了。 - mvndaai
1
响应:= getMD5(fmt.Sprintf(“%s:%s:%08x:%s:%s:%s”,ha1,d [“nonce”],nonceCount,cnonce,d [“qop”],ha2))去掉%08x部分,它对我不起作用。 - bahadir

1
标准库中的net/http包不支持开箱即用的摘要身份验证。然而,可以使用第三方包来实现此功能:
package main

import (
    "net/http"

    "github.com/icholy/digest"
)

func main() {
    client := &http.Client{
        Transport: &digest.Transport{
            Username: "foo",
            Password: "bar",
        },
    }
    res, err := client.Post("https://example.com/api", "text/plain", nil)
    if err != nil {
        panic(err)
    }
    defer res.Body.Close()
}
  • pkgsite {{链接1:https://pkg.go.dev/github.com/icholy/digest}}
  • github {{链接2:https://github.com/icholy/digest}}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接