HTTP Digest 认证:原理剖析与服务端实现详解
HTTP Digest 认证:原理剖析与服务端实现详解
HTTP 协议中的 Digest 认证(摘要认证)是一种比 Basic 认证更安全的身份验证机制,其核心设计是避免密码明文传输,并通过动态随机数(Nonce)防范重放攻击。本文将结合标准协议流程与具体服务端代码实现,深入解析 Digest 认证的技术细节。
一、Digest 认证的核心原理与流程
1.1 为什么需要 Digest 认证?
Basic 认证直接通过 Base64 编码传输用户名和密码(如 username:password
),虽然编码可逆但无加密,安全性极差。Digest 认证通过哈希算法(如 MD5)对密码进行处理,客户端仅传输密码的哈希值(而非明文),且每次请求使用动态随机数(Nonce),大幅提升了安全性。
1.2 标准协议流程(RFC 7616)
Digest 认证的核心是 “挑战 - 响应” 模式,完整流程可分为 4 步:
步骤 1:客户端首次请求受保护资源
客户端访问服务端的受保护路径(如示例中的 /sd
),未携带认证信息。
步骤 2:服务端返回 401 挑战(Challenge)
服务端返回状态码 401 Unauthorized
,并在 WWW-Authenticate
头部中携带以下关键参数:
realm
:认证域(如MyProtectedSD
),用于提示用户认证的上下文(如 “我的受保护存储”)。nonce
:服务端生成的一次性随机数(如a1b2c3...
),用于防止重放攻击。opaque
:服务端生成的固定字符串(如 MD5 哈希值),客户端需原样返回。qop
:认证质量(Quality of Protection),常见值为auth
(仅验证请求)。algorithm
:哈希算法(默认MD5
)。
示例头部:
WWW-Authenticate: Digest realm="MyProtectedSD", qop="auth", nonce="a1b2c3...", opaque="d4e5f6...", algorithm="MD5"
步骤 3:客户端构造认证响应(Response)
客户端收到挑战后,提示用户输入用户名和密码,计算并组装 Authorization
头部,包含以下参数:
-
username
:用户输入的用户名。 -
realm
:服务端返回的realm
(需与本地存储的密码关联)。 -
nonce
:服务端返回的nonce
(原样使用)。 -
uri
:请求的资源路径(如/sd
)。 -
response:核心哈希值,通过以下公式计算:
response = MD5(HA1:nonce:nc:cnonce:qop:HA2)
其中:
HA1 = MD5(username:realm:password)
(客户端预计算的用户密码哈希)。HA2 = MD5(method:uri)
(请求方法与资源路径的哈希,如GET:/sd
)。nc
:请求计数(Nonce Count,防止重放攻击的递增序号)。cnonce
:客户端生成的随机数(Client Nonce,增强随机性)。
步骤 4:服务端验证响应
服务端收到 Authorization
头部后,根据以下逻辑验证:
- 校验
realm
、nonce
(是否有效且未过期)、opaque
是否匹配。 - 根据用户名获取存储的密码(或预计算的
HA1
)。 - 重新计算
HA1
、HA2
和期望的response
。 - 比较客户端提供的
response
与服务端计算的response
,一致则认证成功。
二、服务端实现逻辑:代码解析
本文提供的服务端代码基于 Python 的 http.server
模块,实现了 Digest 认证的核心逻辑。以下是关键模块的详细解析。
2.1 全局配置与状态管理
# --- 配置 ---
SERVER_ADDRESS = '0.0.0.0' # 监听所有接口,方便测试
SERVER_PORT = 8001
REALM = "MyProtectedSD" # 保护域名,会显示在客户端的认证提示中# 存储用户名和密码(在实际应用中,密码应该哈希存储,这里为了演示方便直接存储)
# 或者更好的方式是存储 HA1 = MD5(username:realm:password)
# 例如: HA1 = hashlib.md5(f"your_username_here:{REALM}:your_password_here".encode()).hexdigest()
USERS_PASSWORDS = {"admin001": "my_password_random_generation"
}# 存储已发出的 nonce 及其创建时间,用于校验和防止重放
# {nonce_value: creation_timestamp}
# 实际应用中,nonce 应该有过期机制,并且用后即焚或严格校验 nc (nonce count)
active_nonces = {}
NONCE_LIFETIME_SECONDS = 300 # Nonce 有效期,例如 5 分钟# Opaque 值,服务器生成,客户端原样返回
OPAQUE = hashlib.md5(os.urandom(16)).hexdigest()
- 用户存储:示例中直接存储明文密码(实际生产环境应存储预计算的
HA1 = MD5(username:realm:password)
)。 - nonce 管理:
active_nonces
字典记录nonce
及其生成时间,用于后续过期校验。 - opaque:服务端生成的固定字符串,防止客户端篡改挑战参数。
2.2 nonce 生成与过期校验
def generate_nonce():"""生成唯一的 nonce 并记录创建时间"""nonce = hashlib.md5((os.urandom(16).hex() + str(time.time())).encode()).hexdigest()active_nonces[nonce] = time.time() # 存储 nonce 与时间戳return nonce
nonce
是 Digest 认证的安全基石,其生成需满足:
- 随机性:通过
os.urandom(16)
生成随机字节,结合时间戳确保唯一性。 - 时效性:
active_nonces
记录生成时间,超过NONCE_LIFETIME_SECONDS
(5 分钟)后失效,防止重放攻击。
2.3 挑战响应(401 状态码)
def send_401_challenge(self, stale=False):"""发送 401 Unauthorized 响应和 WWW-Authenticate 挑战头"""self.send_response(401)self.send_header('Content-type', 'text/plain')nonce = generate_nonce() # 生成新 nonceauth_challenge = f'Digest realm="{REALM}", qop="auth", nonce="{nonce}", opaque="{OPAQUE}", algorithm="MD5"'if stale:auth_challenge += ', stale="true"' # 标记旧 nonce 过期,提示客户端使用新 nonceself.send_header('WWW-Authenticate', auth_challenge)self.end_headers()self.wfile.write(b"Authentication required.")print(f"Sent 401 challenge with new nonce: {nonce}")
当客户端未携带认证信息或认证失败时,服务端返回 401
状态码,并通过 WWW-Authenticate
头部发送挑战参数。若 stale=True
(如 nonce
过期),客户端会自动使用新 nonce
重试。
2.4 认证头解析与验证
2.4.1 解析 Authorization 头部
def parse_digest_auth_header(auth_header_value):"""解析 Authorization: Digest ... 头部字符串返回一个包含各参数的字典"""if not auth_header_value or not auth_header_value.lower().startswith('digest '):return Noneauth_parts = {}# 移除 "Digest " 前缀value_str = auth_header_value[len('Digest '):]# 使用正则表达式解析 key="value" 或 key=value 对# 这个正则表达式处理了带引号和不带引号的值pattern = re.compile(r'(\w+)=(?:"([^"]*)"|([^\s,]*))')for match in pattern.finditer(value_str):key = match.group(1)# 值可能在 group(2) (带引号) 或 group(3) (不带引号)val = match.group(2) if match.group(2) is not None else match.group(3)auth_parts[key] = valreturn auth_parts
客户端发送的 Authorization
头部是一个复杂的字符串(如 Digest username="admin001", realm="MyProtectedSD", nonce="a1b2c3", ...
),此函数通过正则表达式提取各参数,供后续验证使用。
2.4.2 验证认证响应
def verify_digest_response(self, auth_parts):"""校验客户端提供的 Digest 认证信息"""required_keys = ['username', 'realm', 'nonce', 'uri', 'response', 'qop', 'nc', 'cnonce']for key in required_keys:if key not in auth_parts:print(f"Missing digest auth key: {key}")return Falseusername = auth_parts.get('username')client_realm = auth_parts.get('realm')client_nonce = auth_parts.get('nonce')uri = auth_parts.get('uri')client_response = auth_parts.get('response')qop = auth_parts.get('qop')nc = auth_parts.get('nc') # nonce countcnonce = auth_parts.get('cnonce') # client noncealgorithm = auth_parts.get('algorithm', 'MD5').upper() # 默认为 MD5# 1. 校验 Realmif client_realm != REALM:print(f"Realm mismatch: expected '{REALM}', got '{client_realm}'")return False# 2. 校验 Nonce# 检查 nonce 是否由服务器发出且未过期# 实际应用中,还需要检查 nc (nonce count) 以防止重放攻击 (nc 应该单调递增)# 这里简化处理:只检查 nonce 是否存在且未超时if client_nonce not in active_nonces:print(f"Invalid nonce: {client_nonce} (not issued by server)")# 可以考虑发送 stale=true,让客户端用新 nonce 重试return "stale" # 特殊返回值表示 nonce 过期if time.time() - active_nonces[client_nonce] > NONCE_LIFETIME_SECONDS:print(f"Nonce expired: {client_nonce}")del active_nonces[client_nonce] # 删除过期的 noncereturn "stale" # 特殊返回值表示 nonce 过期# 3. 校验 Opaque (如果服务器在挑战中发送了)if 'opaque' in auth_parts and auth_parts.get('opaque') != OPAQUE:print(f"Opaque mismatch")return False# 4. 获取用户密码 (或预计算的 HA1)password = USERS_PASSWORDS.get(username)if not password:print(f"Unknown user: {username}")return False# 5. 计算 HA1# HA1 = MD5(username:realm:password)ha1_str = f"{username}:{REALM}:{password}"ha1 = hashlib.md5(ha1_str.encode('utf-8')).hexdigest()print(f"Calculated HA1 for {username}: {ha1}")# 6. 计算 HA2# HA2 = MD5(method:uri)# 注意: self.command 是 HTTP 方法 (e.g., "GET")# uri 是客户端在 Authorization 头中提供的 URIha2_str = f"{self.command}:{uri}"ha2 = hashlib.md5(ha2_str.encode('utf-8')).hexdigest()print(f"Calculated HA2 for {self.command}:{uri}: {ha2}")# 7. 计算期望的 response# response = MD5(HA1:nonce:nc:cnonce:qop:HA2)if qop == "auth" or qop == "auth-int": # auth-int 需要校验 body,这里简化expected_response_str = f"{ha1}:{client_nonce}:{nc}:{cnonce}:{qop}:{ha2}"else:# 如果 qop 不存在 (较老的 RFC 2069 规范,requests 不会这样)expected_response_str = f"{ha1}:{client_nonce}:{ha2}"expected_response = hashlib.md5(expected_response_str.encode('utf-8')).hexdigest()print(f"Expected response: {expected_response}")print(f"Client response: {client_response}")# 8. 比较 responseif client_response == expected_response:# 认证成功后,可以考虑使当前 nonce 失效(或严格检查 nc)# del active_nonces[client_nonce] # 如果 nonce 只能使用一次return Trueelse:print("Response mismatch.")return False
此函数是服务端认证的核心逻辑,通过 8 步校验确保客户端的合法性:
- 参数完整性:检查必要参数(如
username
、nonce
)是否存在。 - realm 匹配:确保客户端请求的认证域与服务端配置一致。
- nonce 有效性:验证
nonce
是否由服务端生成且未过期(防止重放攻击)。 - opaque 校验:确保客户端未篡改挑战参数。
- 哈希计算:重新计算
HA1
(用户密码哈希)和HA2
(请求信息哈希),并生成期望的response
,与客户端提供的response
比较。
2.5 请求处理(do_GET 方法)
def do_GET(self):parsed_path = urlparse(self.path)# 只对 /sd 路径进行认证if parsed_path.path == '/sd':auth_header = self.headers.get('Authorization')if not auth_header:print("No Authorization header, sending 401 challenge.")self.send_401_challenge()returnauth_parts = parse_digest_auth_header(auth_header)if not auth_parts:print("Malformed Authorization header, sending 401 challenge.")self.send_401_challenge() # 或发送 400 Bad Requestreturnverification_result = self.verify_digest_response(auth_parts)if verification_result == "stale":print("Nonce was stale, sending 401 challenge with stale=true.")self.send_401_challenge(stale=True)elif verification_result:print("Authentication successful!")self.send_response(200)self.send_header('Content-type', 'application/json')self.end_headers()response_data = {"message": "Welcome to the secure data area!", "user": auth_parts.get('username')}import jsonself.wfile.write(json.dumps(response_data).encode('utf-8'))else:print("Authentication failed, sending 401 challenge again.")# 认证失败,可以简单地再次发送 401 (可能用新的 nonce)# 或者根据具体策略,如果尝试次数过多可以发送 403 Forbiddenself.send_401_challenge()else:self.send_response(200)self.send_header('Content-type', 'text/plain')self.end_headers()self.wfile.write(b"This is an open area.")
服务端通过 do_GET
方法处理请求:
- 若请求路径为
/sd
(受保护资源),则检查Authorization
头部。 - 无认证头或解析失败时,返回
401
挑战。 - 认证成功后返回资源(如示例中的 JSON 数据)。
- 其他路径(如根路径)直接返回公开内容。
三、关键技术点与安全增强
3.1 nonce 的时效性与重放攻击防范
- 时效性:
nonce
仅在NONCE_LIFETIME_SECONDS
(5 分钟)内有效,过期后服务端删除记录,客户端需重新获取新nonce
。 - 重放攻击:通过
nc
(请求计数)可以进一步防范 —— 客户端每次使用同一nonce
时,nc
必须递增(如从00000001
到00000002
),服务端若发现nc
未递增或重复,则判定为重放攻击。
3.2 密码存储的最佳实践
示例中直接存储明文密码(仅为演示),实际生产环境应存储预计算的 HA1
(MD5(username:realm:password)
)。这样即使数据库泄露,攻击者也无法直接获取密码明文,需结合 realm
和 username
才能计算 HA1
,进一步增强安全性。
3.3 与 Basic 认证的对比
特性 | Basic 认证 | Digest 认证 |
---|---|---|
密码传输方式 | Base64 编码明文(可逆) | 哈希值(不可逆) |
防重放攻击 | 不支持 | 支持(通过 nonce、nc) |
安全性 | 低(易被中间人截获明文) | 高(无明文传输,动态随机数) |
客户端支持 | 所有主流浏览器 | 所有主流浏览器 |
四、测试与验证
4.1 启动服务端
运行代码后,服务端监听 0.0.0.0:8001
,保护路径为 /sd
。
4.2 测试请求
使用 requests
模拟客户端请求:
res = requests.get('http://127.0.0.1:8001/sd',auth=HTTPDigestAuth('admin001', 'my_password_random_generation'))
print(f"Status: {res.status_code}")
print(f"Request Authorization: {res.request.headers['Authorization']}")
print(f"Headers: {res.headers}")
print(f"Response Text: {res.text}")
requests
客户端会自动处理挑战 - 响应流程,请求后打印,可见如下响应信息:
Status: 200
Request Authorization: Digest username="admin001", realm="MyProtectedSD", nonce="f4b50139aeb242406e92e3a24a14f286", uri="/sd", response="72c8e7cf046193a3bce3fb80ec1ce4f6", opaque="b5a7e1bf338b6f1d6c70204c64fd9473", algorithm="MD5", qop="auth", nc=00000001, cnonce="0e22129e06725aa6"
Headers: {'Server': 'BaseHTTP/0.6 Python/3.12.9', 'Date': 'Thu, 22 May 2025 08:41:52 GMT', 'Content-type': 'application/json'}
Response Text: {"message": "Welcome to the secure data area!", "user": "admin001"}
服务端打印关键信息,可见客户端请求流程及认证明细情况:
No Authorization header, sending 401 challenge.
127.0.0.1 - "GET /sd HTTP/1.1" 401 -
Sent 401 challenge with new nonce: f4b50139aeb242406e92e3a24a14f286
Calculated HA1 for admin001: fb5c1f99227711de645d65e5b091f978
Calculated HA2 for GET:/sd: 7c7e535b35fb1070562dec4be2da7ee5
Expected response: 72c8e7cf046193a3bce3fb80ec1ce4f6
Client response: 72c8e7cf046193a3bce3fb80ec1ce4f6
Authentication successful!
127.0.0.1 - "GET /sd HTTP/1.1" 200 -
若认证成功,服务端返回文本:
{"message": "Welcome to the secure data area!", "user": "admin001"}
4.3 验证 nonce 过期
等待 5 分钟后,若使用之前的 nonce
再次请求,服务端会返回 stale=true
的挑战头,提示客户端使用新 nonce
。
五、服务端实现整体代码
import hashlib
import time
import os
import re
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import urlparse# --- 配置 ---
SERVER_ADDRESS = '0.0.0.0' # 监听所有接口,方便测试
SERVER_PORT = 8001
REALM = "MyProtectedSD" # 保护域名,会显示在客户端的认证提示中# 存储用户名和密码(在实际应用中,密码应该哈希存储,这里为了演示方便直接存储)
# 或者更好的方式是存储 HA1 = MD5(username:realm:password)
# 例如: HA1 = hashlib.md5(f"your_username_here:{REALM}:your_password_here".encode()).hexdigest()
USERS_PASSWORDS = {"admin001": "my_password_random_generation"
}# 存储已发出的 nonce 及其创建时间,用于校验和防止重放
# {nonce_value: creation_timestamp}
# 实际应用中,nonce 应该有过期机制,并且用后即焚或严格校验 nc (nonce count)
active_nonces = {}
NONCE_LIFETIME_SECONDS = 300 # Nonce 有效期,例如 5 分钟# Opaque 值,服务器生成,客户端原样返回
OPAQUE = hashlib.md5(os.urandom(16)).hexdigest()def generate_nonce():"""生成一个唯一的 nonce 并记录创建时间"""nonce = hashlib.md5((os.urandom(16).hex() + str(time.time())).encode()).hexdigest()active_nonces[nonce] = time.time() # 记录 nonce 创建时间return noncedef parse_digest_auth_header(auth_header_value):"""解析 Authorization: Digest ... 头部字符串返回一个包含各参数的字典"""if not auth_header_value or not auth_header_value.lower().startswith('digest '):return Noneauth_parts = {}# 移除 "Digest " 前缀value_str = auth_header_value[len('Digest '):]# 使用正则表达式解析 key="value" 或 key=value 对# 这个正则表达式处理了带引号和不带引号的值pattern = re.compile(r'(\w+)=(?:"([^"]*)"|([^\s,]*))')for match in pattern.finditer(value_str):key = match.group(1)# 值可能在 group(2) (带引号) 或 group(3) (不带引号)val = match.group(2) if match.group(2) is not None else match.group(3)auth_parts[key] = valreturn auth_partsclass DigestAuthHandler(BaseHTTPRequestHandler):def send_401_challenge(self, stale=False):"""发送 401 Unauthorized 响应和 WWW-Authenticate 挑战头"""self.send_response(401)self.send_header('Content-type', 'text/plain')nonce = generate_nonce() # 生成新 nonceauth_challenge = f'Digest realm="{REALM}", qop="auth", nonce="{nonce}", opaque="{OPAQUE}", algorithm="MD5"'if stale:auth_challenge += ', stale="true"' # 标记旧 nonce 过期,提示客户端使用新 nonceself.send_header('WWW-Authenticate', auth_challenge)self.end_headers()self.wfile.write(b"Authentication required.")print(f"Sent 401 challenge with new nonce: {nonce}")def verify_digest_response(self, auth_parts):"""校验客户端提供的 Digest 认证信息"""required_keys = ['username', 'realm', 'nonce', 'uri', 'response', 'qop', 'nc', 'cnonce']for key in required_keys:if key not in auth_parts:print(f"Missing digest auth key: {key}")return Falseusername = auth_parts.get('username')client_realm = auth_parts.get('realm')client_nonce = auth_parts.get('nonce')uri = auth_parts.get('uri')client_response = auth_parts.get('response')qop = auth_parts.get('qop')nc = auth_parts.get('nc') # nonce countcnonce = auth_parts.get('cnonce') # client noncealgorithm = auth_parts.get('algorithm', 'MD5').upper() # 默认为 MD5# 1. 校验 Realmif client_realm != REALM:print(f"Realm mismatch: expected '{REALM}', got '{client_realm}'")return False# 2. 校验 Nonce# 检查 nonce 是否由服务器发出且未过期# 实际应用中,还需要检查 nc (nonce count) 以防止重放攻击 (nc 应该单调递增)# 这里简化处理:只检查 nonce 是否存在且未超时if client_nonce not in active_nonces:print(f"Invalid nonce: {client_nonce} (not issued by server)")# 可以考虑发送 stale=true,让客户端用新 nonce 重试return "stale" # 特殊返回值表示 nonce 过期if time.time() - active_nonces[client_nonce] > NONCE_LIFETIME_SECONDS:print(f"Nonce expired: {client_nonce}")del active_nonces[client_nonce] # 删除过期的 noncereturn "stale" # 特殊返回值表示 nonce 过期# 3. 校验 Opaque (如果服务器在挑战中发送了)if 'opaque' in auth_parts and auth_parts.get('opaque') != OPAQUE:print(f"Opaque mismatch")return False# 4. 获取用户密码 (或预计算的 HA1)password = USERS_PASSWORDS.get(username)if not password:print(f"Unknown user: {username}")return False# 5. 计算 HA1# HA1 = MD5(username:realm:password)ha1_str = f"{username}:{REALM}:{password}"ha1 = hashlib.md5(ha1_str.encode('utf-8')).hexdigest()print(f"Calculated HA1 for {username}: {ha1}")# 6. 计算 HA2# HA2 = MD5(method:uri)# 注意: self.command 是 HTTP 方法 (e.g., "GET")# uri 是客户端在 Authorization 头中提供的 URIha2_str = f"{self.command}:{uri}"ha2 = hashlib.md5(ha2_str.encode('utf-8')).hexdigest()print(f"Calculated HA2 for {self.command}:{uri}: {ha2}")# 7. 计算期望的 response# response = MD5(HA1:nonce:nc:cnonce:qop:HA2)if qop == "auth" or qop == "auth-int": # auth-int 需要校验 body,这里简化expected_response_str = f"{ha1}:{client_nonce}:{nc}:{cnonce}:{qop}:{ha2}"else:# 如果 qop 不存在 (较老的 RFC 2069 规范,requests 不会这样)expected_response_str = f"{ha1}:{client_nonce}:{ha2}"expected_response = hashlib.md5(expected_response_str.encode('utf-8')).hexdigest()print(f"Expected response: {expected_response}")print(f"Client response: {client_response}")# 8. 比较 responseif client_response == expected_response:# 认证成功后,可以考虑使当前 nonce 失效(或严格检查 nc)# del active_nonces[client_nonce] # 如果 nonce 只能使用一次return Trueelse:print("Response mismatch.")return Falsedef do_GET(self):parsed_path = urlparse(self.path)# 只对 /sd 路径进行认证if parsed_path.path == '/sd':auth_header = self.headers.get('Authorization')if not auth_header:print("No Authorization header, sending 401 challenge.")self.send_401_challenge()returnauth_parts = parse_digest_auth_header(auth_header)if not auth_parts:print("Malformed Authorization header, sending 401 challenge.")self.send_401_challenge() # 或发送 400 Bad Requestreturnverification_result = self.verify_digest_response(auth_parts)if verification_result == "stale":print("Nonce was stale, sending 401 challenge with stale=true.")self.send_401_challenge(stale=True)elif verification_result:print("Authentication successful!")self.send_response(200)self.send_header('Content-type', 'application/json')self.end_headers()response_data = {"message": "Welcome to the secure data area!", "user": auth_parts.get('username')}import jsonself.wfile.write(json.dumps(response_data).encode('utf-8'))else:print("Authentication failed, sending 401 challenge again.")# 认证失败,可以简单地再次发送 401 (可能用新的 nonce)# 或者根据具体策略,如果尝试次数过多可以发送 403 Forbiddenself.send_401_challenge()else:self.send_response(200)self.send_header('Content-type', 'text/plain')self.end_headers()self.wfile.write(b"This is an open area.")def log_message(self, format, *args):"""覆盖默认日志,方便调试"""print(f"{self.address_string()} - {format % args}")def run_server(server_class=HTTPServer, handler_class=DigestAuthHandler, addr=SERVER_ADDRESS, port=SERVER_PORT):server_address = (addr, port)httpd = server_class(server_address, handler_class)print(f"Starting Digest Auth server on {addr}:{port}...")print(f"Protected path: /sd")print(f"Test with username: '{USERS_PASSWORDS.keys()}', password: '{USERS_PASSWORDS.values()}'")try:httpd.serve_forever()except KeyboardInterrupt:print("\nServer shutting down.")finally:httpd.server_close()if __name__ == '__main__':run_server()
小总结
HTTP Digest 认证通过哈希算法和动态随机数(nonce)解决了 Basic 认证的明文传输问题,是轻量级场景下的安全认证方案。本文结合代码详细解析了其核心流程(挑战 - 响应)和服务端实现逻辑(nonce 管理、哈希计算、响应验证),并强调了生产环境中的安全增强点(如存储 HA1
、校验 nc
)。实际应用中,建议结合 HTTPS 进一步加密传输过程,以达到更高的安全性。
————————————————
Java猿社区—Http digest authentication 请求代码最全示例 - 简书
HTTP认证之摘要认证——Digest(二) - xiaoxiaotank - 博客园
HTTP的几种认证方式之DIGEST 认证(摘要认证) - wenbin_ouyang - 博客园
HTTP Authentication之Basic认证、Digest认证
http digest鉴权流程
python http 身份认证简介
相关文章:
HTTP Digest 认证:原理剖析与服务端实现详解
HTTP Digest 认证:原理剖析与服务端实现详解 HTTP 协议中的 Digest 认证(摘要认证)是一种比 Basic 认证更安全的身份验证机制,其核心设计是避免密码明文传输,并通过动态随机数(Nonce)防范重放攻…...

untiy实现汽车漫游
实现效果 汽车漫游 1.创建汽车模型 导入汽车模型(FBX格式或其他3D格式),确保模型包含车轮、车身等部件。 为汽车添加碰撞体(如 Box Collider 或 Mesh Collider),避免穿透场景物体。 添加 Rigidbody 组件,启用重力并调整质量(Mass)以模拟物理效果。 2.编写汽车控制脚本…...

PID项目---硬件设计
该项目是立创训练营项目,这些是我个人学习的记录,记得比较潦草 1.硬件-电路原理电赛-TI-基于MSPM0的简易PID项目_哔哩哔哩_bilibili 这个地方接地是静电的考量 这个保护二极管是为了在电源接反的时候保护电脑等设备 大电容的作用:当电机工作…...

Pluto实验报告——基于FM的音频信号传输并解调恢复
目录 一、实验目的 ................................ ................................ ................................ .................. 3 二、实验内容 ................................ ................................ ................................ ......…...
【Redis】AOF日志
目录 1、背景2、工作原理3、核心配置参数4、优缺点5、AOF文件内容 1、背景 AOF(Append Only File)是redis提供的持久化机制之一,它通过记录所有修改数据库状态的写命令来实现数据库持久化。与RDB(快照)方式不同&#…...

Leetcode 2792. 计算足够大的节点数
1.题目基本信息 1.1.题目描述 给定一棵二叉树的根节点 root 和一个整数 k 。如果一个节点满足以下条件,则称其为 足够大 : 它的子树中 至少 有 k 个节点。 它的值 大于 其子树中 至少 k 个节点的值。返回足够大的节点数。 如果 u v 或者 v 是 u 的…...
《关于浔川社团退出DevPress社区及内容撤回的声明》
《关于浔川社团退出DevPress社区及内容撤回的声明》 尊敬的DevPress社区及读者: 经浔川社团内部决议,我社决定自**2025年5月26日**起正式退出DevPress社区,并撤回所有由我社成员在该平台发布的原创文章。相关事项声明如下: …...
Windows逆向工程提升之IMAGE_RESOURCE_DIRECTORY
公开视频 -> 链接点击跳转公开课程博客首页 -> 链接点击跳转博客主页 目录 资源目录概述 什么是资源目录? 资源目录的作用 资源目录的位置 资源目录核心结构 IMAGE_RESOURCE_DIRECTORY IMAGE_RESOURCE_DIRECTORY_ENTRY IMAGE_RESOURCE_DATA_EN…...

使用ps为图片添加水印
打开图片 找到文字工具 输入想要添加的水印 使用移动工具移动到合适的位置 选中文字图层 设置不透明度 快捷键ctrlt可以旋转 另存为png格式图片...

x64_ubuntu22.04.5安装:cuda driver + cuda toolkit
引言 本文操作均已实践验证,安装流程来自nvidia官方文档,验证平台显卡:RTX4070。 验证日期:2025.5.24. 1.安装cuda driver 1.1.安装方式有2种,这里选择方式1: 从apt安装最省事💖,…...

开盘啦 APP 抓包 逆向分析
声明: 本文章中所有内容仅供学习交流使用,不用于其他任何目的,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关! 抓包 这是一个记录贴。 这个APP是数…...

vs2022 Qt Visual Studio Tools插件设置
安装之后,需要指定QT中msvc编译器的位置,点击下图Location右边的按钮即可 选择msvc2022_64\bin目录下的 qmake.exe 另一个问题,双击UI文件不能打开设计界面 设置打开方式 选择msvc2022_64\bin目录下的designer.exe 确定即可 然后设置为默认值即可 确定…...

Python包__init__.py标识文件解析
在 Python 中,__init__.py 文件是包(Package)的核心标识文件,它的存在使一个目录被 Python 解释器识别为「包」。这个文件有以下核心作用: 核心作用 标识包的存在 任何包含 __init__.py 的目录都会被 Python 视为一个包…...
【MySQL】第8节|Innodb底层原理与Mysql日志机制深入剖析(一)
MySQL 的 redo log(重做日志) redo log 是 MySQL 中 InnoDB 存储引擎实现事务持久性的关键机制,用于记录数据库数据的变更,确保事务提交后数据不丢失,即使发生宕机也能通过日志恢复数据。 核心作用 1. 实现事务的持…...

电商ERP管理系统,Java+Vue,含源码与文档,统筹订单、库存等,助力电商企业高效运营
前言: 在当今数字化飞速发展的电商时代,电商企业面临着日益激烈的市场竞争和复杂的业务运营环境。为了提升运营效率、降低成本、优化客户体验,一套高效、全面的电商ERP管理系统显得尤为重要。电商ERP管理系统整合了企业内部的各项业务流程&a…...

Spring Boot微服务架构(四):微服务的划分原则
微服务划分原则(CRM系统案例说明) 一、微服务划分的核心原则 单一职责原则(SRP) 每个微服务只负责一个明确的业务功能服务边界清晰,避免功能混杂便于独立开发、测试和部署 业务领域驱动设计(DDD࿰…...

【打卡】树状数组的操作
#define MAXN 1000 int n; // 数组实际长度 int array[MAXN]; // 原始数组(下标从0开始) int tree[MAXN]; // 树状数组(下标从1开始) int p[MAXN]; // 前缀和数组(下标从1…...
OpenLayers 加载动画控件
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 地图控件是一些用来与地图进行简单交互的工具,地图库预先封装好,可以供开发者直接使用。OpenLayers具有大部分常用的控件&#x…...
Oracle 基础知识作业的使用
对于DBA来说,数据库Job再熟悉不过了,因为经常要数据库定时的自动执行一些脚本,或做数据库备份,或做数据的提炼,或做数据库的性能优化,包括重建索引等等的工作。 Oracle 视图 User_Jobs 是Oracle数据库中的一…...

HTTP协议初认识、速了解
目录 1. 什么是HTTP协议 2. HTTP协议特点 3. HTTP协议发展和版本 3.1. HTTP1.0 3.2. HTTP1.1 3.3. HTTP2.0 3.4. http1.1和http2.0区别 4. HTTP协议中URI、URL、URN 4.1. URI 4.2. URL 4.3. URN 5. HTTP协议的请求 5.1. HTTP协议中的请求信息 5. 总结 前言 本文讲…...
C#:多线程Task使用
一.Task与Thread Task是架构在Thread之上的,也就是说任务最终还是要抛给线程去执行。Task跟Thread不是一对一的关系,比如开10个任务并不是说会开10个线程,这一点任务有点类似线程池,但是任务相比线程池有很小的开销和精确的控制。…...

模拟电子技术基础----绪论
一、电子技术的发展 1.电子技术的发展,推动计算机技术的发展,使之“无孔不入”,应用广泛! •广播通信:发射机、接收机、扩音、录音、程控交换机、电话、手机 •网络:路由器、ATM交换机、收发器、调制解调…...
从零基础到最佳实践:Vue.js 系列(2/10):《模板语法与数据绑定》
Vue.js 模板语法与数据绑定:从基础到实践 关键点 Vue.js 的模板语法使用 HTML 结合特殊指令(如 v-bind、v-on),实现动态界面。插值({{ }})显示数据,指令控制 DOM 行为,双向绑定简化…...

iOS 使用 - 设置 来电震动/关闭震动
来电震动是一个很直观的老功能。但到了iOS 18,苹果却把震动功能的开关藏得越来越深,甚至分散在不同的菜单里,让用户难以找到。这里记录分享设置方法: 1. 震动开关的路径 设置 → 通用 → 辅助功能 → 触控 → 震动 2. 来电震动…...
anaconda、miniconda、conda的关系及miniconda安装
anaconda、miniconda、conda的关系及miniconda安装 文章目录 前言正文定义关系Linux安装miniconda新建一个python3.8环境 参考 前言 本文用于记录关于Anaconda、conda和Miniconda的定义及其关系的总结123: 正文 定义 conda 一个跨平台的开源包管理和环境管理工具…...

[C语言初阶]扫雷小游戏
目录 一、原理及问题分析二、代码实现2.1 分文件结构设计2.2 棋盘初始化与打印2.3 布置雷与排查雷2.4 游戏主流程实现 三、后期优化方向 在上一篇文章中,我们实现了我们的第二个游戏——三子棋小游戏。这次我们继续结合我们之前所学的所有内容,制作出我们…...

谷歌medgemma-27b-text-it医疗大模型论文速读:多语言大型语言模型医学问答基准测试MedExpQA
《MedExpQA: 多语言大型语言模型医学问答基准测试》论文解析 一、引言 论文开篇指出大型语言模型(LLMs)在医学领域的巨大潜力,尤其是在医学问答(QA)方面。尽管LLMs在医学执照考试等场景中取得了令人瞩目的成绩&#…...
Lambda表达式的高级用法
今天来分享下Java的Lambda表达式,以及它的高级用法。 使用它可以提高代码的简洁度,使代码更优雅。 一、什么是lambda表达式 Lambda 表达式是 Java 8 引入的特性,用于简化匿名内部类的语法,使代码更简洁,尤其在处理函…...
速盾(sudun):如何利用CDN技术实现页面加速?
随着互联网内容的爆炸式增长,用户对网页加载速度的要求也越来越高。快速加载的网页不仅能提升用户体验,还能直接影响搜索引擎排名和网站转化率。内容分发网络(CDN)作为一种有效的解决方案,通过在全球范围内部署多个高性…...

DeepSeek+白果AI论文:开启答辩PPT生成的「智能双引擎」时代
2025学术答辩革新:DeepSeek与白果AI论文的黄金协同方案 白果Ai论文,论文写作神器~ https://www.baiguoai.com/ 在学术答辩的「战场」上,「选题创新不足」「数据可视化低效」「PPT逻辑断裂」等痛点长期困扰研究者。DeepSeek与白果AI论文的深…...