我正在尝试使用需要摘要认证的Gerrit API。经过一些阅读,我知道我应该发送一个请求,得到一个401错误,然后使用领域和随机数以及可能的其他头信息来使用MD5创建实际请求认证。我找到了一些关于摘要的示例,但它们似乎都是服务器端的,而不是客户端的。
我正在尝试使用需要摘要认证的Gerrit API。经过一些阅读,我知道我应该发送一个请求,得到一个401错误,然后使用领域和随机数以及可能的其他头信息来使用MD5创建实际请求认证。我找到了一些关于摘要的示例,但它们似乎都是服务器端的,而不是客户端的。
我主要是按照维基百科上关于如何发出请求的说明,然后查看详细的curl -v --digest --user username:password http://url.com/api请求以确定其中的部分。这里是关键部分:您需要发出请求,收到401未授权响应,然后使用未经授权请求标头中的nonce
和realm
基于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
}
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()
}