web用戶認證,最開始是get提交+把用戶名密碼存放在客戶端的cookie中的形式;在乎識到這樣不安全以後逐漸演變成了post提交+把用戶憑證放到了服務端的session中的形式(固然sessionid還在cookie中)。html
不過其實最初給http設計的認證方式,既不是「get+cookie」也不是「post+session」,而是Basic和Digest。但Basic和Digest並不流行我想主要是由於麻煩,一是說Basic和Digest使用的Authorization頭並不會被瀏覽器自動發往服務器,二是說對於Digest計算很麻煩。web
請求示例以下,主要是Authorization頭(位置不重要,http頭通常都不分前後)瀏覽器
GET /GetDeviceInfo HTTP/1.1
Host: 192.168.220.128
Authorization: Basic YWRtaW46MTIzNDU2
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:60.0) Gecko/20100101 Firefox/60.0
Accept-Encoding: gzip, deflate
Accept: */*
Cache-Control: no-cache
Cookie: Secure
Connection: close
前邊請求Authorization頭的YWRtaW46MTIzNDU2,其實是用戶名admin密碼123456使用如下計算方法獲得:安全
base64(username:password)
Python計算代碼以下:服務器
import base64 def get_basic_authorization_header_value(username,password): # base64編碼先後都(要)是字節碼形式 authorization_value = base64.b64encode((f"{username}:{password}").encode()).decode() authorization_header_value = f"Basic {authorization_value}" return authorization_header_value
GET /GetDeviceInfo HTTP/1.1
Host: 192.168.220.128
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:60.0) Gecko/20100101 Firefox/60.0
Authorization: Digest username="admin",realm="TVT API Test Tool",nonce="d4f95e85dc5a39a4914db61b67878f5b",uri="GetDeviceInfo",algorithm="MD5",cnonce="d4f95e85dc5a39a4914db61b67878f5b",nc=00000001,qop="auth",response="1cc4cf126d3c4a70d2de34c5d8c2943c"
Accept-Encoding: gzip, deflate
Accept: */*
Cache-Control: no-cache
Cookie: Secure
Connection: close
username----系統用戶名;客戶端自行填充cookie
realm----領域;服務端經過WWW-Authenticate頭返回內容能夠本身隨便定,但其目的是用於提示客戶端當前是什麼系統,因此規範來講應相似於「myhost@testrealm.com」的形式。session
nonce----服務端經過WWW-Authenticate頭返回的隨機數post
uri----請求接口或資源(彷佛規範來講應用GET或POST後的同樣,上邊例子中少了/是由於服務端沒按規範實現)編碼
algorithm----後邊response用的計算方法spa
cnonce----client nonce,客戶端生成的隨機數
nc----nonce count,用於標識進行請求的次數。(但你一直不變服務端也不會管你對不對)
qop----quality of protection,進一步限定response的計算方法,服務端經過WWW-Authenticate頭返回。
response----認證最主要的值,前面各字段除algorithm外全要參與該值的計算。
在最開始的RFC 2069中規定response計算方法以下:
HA1 = MD5(username:realm:password) HA2 = MD5(method:uri) response = MD5(HA1:nonce:HA2)
隨後的RFC 2617對計算方法進行了加強,規定計算方法以下(當algorithm值爲MD5或未指定、qop未指定時等同RFC 2069):
# HA1部分 # 當algorithm值爲"MD5"或未指定時,HA1計算方法以下 HA1 = MD5(username:realm:password) # 當algorithm值爲"MD5-sess"時,HA1計算方法以下 HA1 = MD5(MD5(username:realm:password):nonce:cnonce) # HA2部分 # 當qop值爲"auth"或未指定時,HA2計算方法以下 HA2 = MD5(method:uri) # 當qop值爲"auth-int"時,HA2計算方法以下;entityBody是指整個body(?) HA2 = MD5(method:uri:MD5(entityBody)) # response部分 # 當qop值爲"auth"或"auth-int"時,response計算方法以下 response = MD5(HA1:nonce:nonceCount:cnonce:qop:HA2) # 當qop未指定時,response計算方法以下 response = MD5(HA1:nonce:HA2)
Python計算代碼以下:
import hashlib # body初始值不要是None,否則下邊encode時會報錯 def get_basic_authorization_header_value(username, password, uri, method, realm, nonce, nc, cnonce, algorithm=None, qop=None, body=""): response_value = calc_digest_response_value(username, password, uri, method, realm, nonce, nc, cnonce, algorithm, qop, body) authorization_header_value = f'Digest username="{username}",realm="{realm}",nonce="{nonce}",uri="{uri}",algorithm="{algorithm}",cnonce="{cnonce}",nc={nc},qop="{qop}",response="{response_value}"', return authorization_header_value def calc_digest_response_value(username, password, uri, method, realm, nonce, nc, cnonce, algorithm=None, qop=None, body=""): # HA1部分 # 當algorithm值爲"MD5"或未指定時,HA1計算方法以下 if algorithm == "MD5" or algorithm == "" or algorithm is None: HA1 = hashlib.md5((f"{username}:{realm}:{password}").encode()).hexdigest() # 當algorithm值爲"MD5-sess"時,HA1計算方法以下 elif algorithm == "MD5-sess": HA1 = hashlib.md5((f"{username}:{realm}:{password}").encode()).hexdigest() HA1 = hashlib.md5((f"{HA1}:{nonce}:{cnonce}").encode()).hexdigest() else: response_value = '"the value of algorithm must be one of "MD5"/"MD5-sess"/""/None' return response_value # HA2部分 # 當qop值爲"auth"或未指定時,HA2計算方法以下 if qop == "auth" or qop == "" or qop is None: HA2 = hashlib.md5((f"{method}:{uri}").encode()).hexdigest() # 當qop值爲"auth-int"時,HA2計算方法以下;entityBody是否是指整個body我其實不太肯定 elif qop == "auth-int": HA2 = hashlib.md5((f"{body}").encode()).hexdigest() HA2 = hashlib.md5((f"{method}:{uri}:{HA2}").encode()).hexdigest() else: response_value = '"the value of qop must be one of "auth"/"auth-int"/""/None' return response_value # response部分 # 當qop值爲"auth"或"auth-int"時,response計算方法以下 if qop == "auth" or qop == "auth-int": response_value = hashlib.md5((f"{HA1}:{nonce}:{nc}:{cnonce}:{qop}:{HA2}").encode()).hexdigest() # 當qop未指定時,response計算方法以下 elif qop == "" or qop is None: response_value = hashlib.md5((f"{HA1}:{nonce}:{HA2}").encode()).hexdigest() else: response_value = "unknown error" return response_value
參考:
https://en.wikipedia.org/wiki/Digest_access_authentication