Http基本认证和摘要认证

HTTP协议原生支持的认证

1.基本认证 Basic Authentication

基本认证只需要将用户名密码-简单方便但是明文传输不安全

未认证响应
状态: 401
头部: WWW-Authenticate 值: Basic realm="IP Camera(L4236)"
客户端具体做法:
1.将 user:password 进行base64编码,结果:dXNlcjpwYXNzd29yZA==
2.给http请求添加头部 Authorization 值为Basic YWRtaW46YWRtaW4=

2.摘要认证 Digest Authentication

摘要认证根据401响应中的算法和随机数对密码进行加密

未认证响应
状态: 401
头部: WWW-Authenticate 值: Digest qop="auth", realm="IP Camera(L4236)", nonce="326435383a37323966343563663a3c2f55408da812d8509c09bb0574faff", stale="FALSE"

Digest:说明是摘要认证
qop: =auth时说明需要认证,=auth-int时需要有完整性保护的认证,其他值忽略。(可选)
realm:当前响应所在的域,根据域就知道该使用哪个用户名密码了,一般包含执行认证功能的主机名。
nonce:服务器生成的随机字符串,用于加密使用,一般是Base64或十六进制数据,Get请求conce包含过期时间,Put或Post要求使用一次性nonce。
stale:为true时说明nonce失效了,需要重新获取,为false是说明nonce仍然有效。
algorithm:说明算法,没有该值时默认使用md5算法。

客户端具体做法:

1.解析WWW-Authenticate获取nonce和加密算法等

2.生成response

2.1 以格式user:realm:password进行MD5加密得到值a1,如admin:IP Camera(L4236):123456

2.2 以格式method:uri进行MD5加密得到值a2,如get:/System/deviceInfo

2.3 以格式a1:nonce:nc:cnonce:qop:a2进行MD5加密得到response,如a1:326435383a37323966343563663a3c2f55408da812d8509c09bb0574faff:00000001:0a4f113b:auth:a2

  1. 生成以下字符串作为http头部 Authorization 的值,然后发送请求:

Digest username="admin", realm="IP Camera(L4236)", nonce="353761313a62373364373163373a94eac188bbb9a6119d58f125dfad1c7a", uri="/System/deviceInfo", algorithm="MD5", qop=auth, nc=1, cnonce="177783ecbdc88e14", response="c3a1cf18e1ebc5f3b237dd4664169262"

go语言实现摘要认证

//digest.go
import (
	"crypto/md5"
	"encoding/json"
	"fmt"
	"log"
	"strings"
)

// Challenge WWW-Authorization头部的值
type Challenge struct {
	Realm     string
	Domain    string
	Nonce     string
	Opaque    string
	Stale     string
	Algorithm string
	Qop       string
}

// Response 作为Authorization头部的值
type Response struct {
	Username string
	Realm    string
	Nonce    string
	URI      string
	Qop      string
	Cnonce   string
	Nc       string
	Response string	//计算出的response
}

func MD5(s string) string {
	return fmt.Sprintf("%x", md5.Sum([]byte(s)))
}

// NewChallenge 根据WWW-Authorization的值生成Challenge
func NewChallenge(header string) *Challenge {
	var m = make(map[string]string, 5)
	str := header[7:]
	split := strings.Split(str, ",")
	for _, v := range split {
		s := strings.Split(v, "=")
		k := strings.Trim(s[0], " ")
		m[k] = strings.Trim(s[1], "\"")
	}

	// Marshall digest
	b, err := json.Marshal(m)
	if err != nil {
		log.Fatalln("Error:", err)
	}

	// Unmarshall into struct
	challenge := Challenge{}
	if err := json.Unmarshal(b, &challenge); err != nil {
		log.Fatalln("Error:", err)
	}
	return &challenge
}

// Authorize 生成 Response  #客户端需要对服务端认证时cnonce应是变化的#
func (c *Challenge) Authorize(username, password, method, uri string) *Response {
	// MD5(<username>:<realm>:<password>)
	a1 := MD5(fmt.Sprintf("%s:%s:%s", username, c.Realm, password))

	// MD5(<request-method>:<uri>)
	a2 := MD5(fmt.Sprintf("%s:%s", method, uri))

	// MD5(MD5(A1):<nonce>:<nc>:<cnonce>:<qop>:MD5(A2))
	response := MD5(fmt.Sprintf("%s:%s:%s:%s:%s:%s", a1, c.Nonce, "00000001", "0a4f113b", c.Qop, a2))
	return &Response{
		Username: username,
		Realm:    c.Realm,
		Nonce:    c.Nonce,
		URI:      uri,
		Qop:      c.Qop,
		Cnonce:   "0a4f113b",
		Nc:       "00000001",
		Response: response,
	}
}

// 生成字符串作为Authorization的值
func (r *Response) String() string {
	return fmt.Sprintf(`Digest username="%s", realm="%s", nonce="%s", uri="%s", qop=%s, nc=%s, cnonce="%s", response="%s"`, r.Username, r.Realm, r.Nonce, r.URI, r.Qop, r.Nc, r.Cnonce, r.Response)
}

//authTransport.go
import (
	"bytes"
	"io/ioutil"
	"net/http"
)

// AuthTransport 实现接口http.RoundTripper,自定义RoundTripper处理认证
type AuthTransport struct {
	Username  string
	Password  string
	Transport http.RoundTripper
}

func NewAuthTransport(username, password string) *AuthTransport {
	t := &AuthTransport{
		Username: username,
		Password: password,
	}
	t.Transport = http.DefaultTransport
	return t
}

func (t *AuthTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	req2 := new(http.Request)
	*req2 = *req
	//复制header
	req2.Header = make(http.Header)
	for k, s := range req.Header {
		req2.Header[k] = s
	}
	// 复制body
	if req.Body != nil {
		buf, _ := ioutil.ReadAll(req.Body)
		req.Body = ioutil.NopCloser(bytes.NewBuffer(buf))
		req2.Body = ioutil.NopCloser(bytes.NewBuffer(buf))
	}
	//
	resp, err := t.Transport.RoundTrip(req)
	if err != nil || resp.StatusCode != 401 {
		return resp, err
	}
	//↓未认证逻辑-----------
	authChal := resp.Header.Get("WWW-Authenticate")
	c := NewChallenge(authChal)
	authResp := c.Authorize(t.Username, t.Password, req.Method, req.URL.Path)
	resp.Body.Close()
	//todo nonce并未重用,可以优化
	req2.Header.Set("Authorization", authResp.String())
	return t.Transport.RoundTrip(req2)
}
//main.go
func main(){
    //获取http客户端
    client := &http.Client{
			Transport: NewAuthTransport(username, password),
		}
    //之后使用该客户端会自动认证
}
posted @ 2023-10-27 20:25  longan55  阅读(354)  评论(0)    收藏  举报