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
- 生成以下字符串作为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),
}
//之后使用该客户端会自动认证
}

浙公网安备 33010602011771号