HTTP Digest 认证:原理剖析与服务端实现详解

news2025/5/25 12:46:02

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 头部后,根据以下逻辑验证:

  1. 校验 realmnonce(是否有效且未过期)、opaque 是否匹配。
  2. 根据用户名获取存储的密码(或预计算的 HA1)。
  3. 重新计算 HA1HA2 和期望的 response
  4. 比较客户端提供的 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()  # 生成新 nonce
    auth_challenge = f'Digest realm="{REALM}", qop="auth", nonce="{nonce}", opaque="{OPAQUE}", algorithm="MD5"'
    if stale:
        auth_challenge += ', stale="true"'  # 标记旧 nonce 过期,提示客户端使用新 nonce
        
    self.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 None
    
    auth_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] = val
    return 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 False

    username = 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 count
    cnonce = auth_parts.get('cnonce')  # client nonce
    algorithm = auth_parts.get('algorithm', 'MD5').upper()  # 默认为 MD5

    # 1. 校验 Realm
    if 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] # 删除过期的 nonce
        return "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 头中提供的 URI
    ha2_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. 比较 response
    if client_response == expected_response:
        # 认证成功后,可以考虑使当前 nonce 失效(或严格检查 nc)
        # del active_nonces[client_nonce] # 如果 nonce 只能使用一次
        return True
    else:
        print("Response mismatch.")
        return False

此函数是服务端认证的核心逻辑,通过 8 步校验确保客户端的合法性:

  • 参数完整性:检查必要参数(如 usernamenonce)是否存在。
  • 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()
            return

        auth_parts = parse_digest_auth_header(auth_header)
        if not auth_parts:
            print("Malformed Authorization header, sending 401 challenge.")
            self.send_401_challenge()  # 或发送 400 Bad Request
            return

        verification_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 json
            self.wfile.write(json.dumps(response_data).encode('utf-8'))
        else:
            print("Authentication failed, sending 401 challenge again.")
            # 认证失败,可以简单地再次发送 401 (可能用新的 nonce)
            # 或者根据具体策略,如果尝试次数过多可以发送 403 Forbidden
            self.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 必须递增(如从 0000000100000002),服务端若发现 nc 未递增或重复,则判定为重放攻击。

3.2 密码存储的最佳实践

示例中直接存储明文密码(仅为演示),实际生产环境应存储预计算的 HA1MD5(username:realm:password))。这样即使数据库泄露,攻击者也无法直接获取密码明文,需结合 realmusername 才能计算 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 nonce


def parse_digest_auth_header(auth_header_value):
    """
    解析 Authorization: Digest ... 头部字符串
    返回一个包含各参数的字典
    """
    if not auth_header_value or not auth_header_value.lower().startswith('digest '):
        return None

    auth_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] = val
    return auth_parts


class 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()  # 生成新 nonce
        auth_challenge = f'Digest realm="{REALM}", qop="auth", nonce="{nonce}", opaque="{OPAQUE}", algorithm="MD5"'
        if stale:
            auth_challenge += ', stale="true"'  # 标记旧 nonce 过期,提示客户端使用新 nonce

        self.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 False

        username = 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 count
        cnonce = auth_parts.get('cnonce')  # client nonce
        algorithm = auth_parts.get('algorithm', 'MD5').upper()  # 默认为 MD5

        # 1. 校验 Realm
        if 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] # 删除过期的 nonce
            return "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 头中提供的 URI
        ha2_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. 比较 response
        if client_response == expected_response:
            # 认证成功后,可以考虑使当前 nonce 失效(或严格检查 nc)
            # del active_nonces[client_nonce] # 如果 nonce 只能使用一次
            return True
        else:
            print("Response mismatch.")
            return False

    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()
                return

            auth_parts = parse_digest_auth_header(auth_header)
            if not auth_parts:
                print("Malformed Authorization header, sending 401 challenge.")
                self.send_401_challenge()  # 或发送 400 Bad Request
                return

            verification_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 json
                self.wfile.write(json.dumps(response_data).encode('utf-8'))
            else:
                print("Authentication failed, sending 401 challenge again.")
                # 认证失败,可以简单地再次发送 401 (可能用新的 nonce)
                # 或者根据具体策略,如果尝试次数过多可以发送 403 Forbidden
                self.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://www.coloradmin.cn/o/2385351.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

untiy实现汽车漫游

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

PID项目---硬件设计

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

Pluto实验报告——基于FM的音频信号传输并解调恢复

目录 一、实验目的 ................................ ................................ ................................ .................. 3 二、实验内容 ................................ ................................ ................................ ......…

Leetcode 2792. 计算足够大的节点数

1.题目基本信息 1.1.题目描述 给定一棵二叉树的根节点 root 和一个整数 k 。如果一个节点满足以下条件,则称其为 足够大 : 它的子树中 至少 有 k 个节点。 它的值 大于 其子树中 至少 k 个节点的值。返回足够大的节点数。 如果 u v 或者 v 是 u 的…

使用ps为图片添加水印

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

x64_ubuntu22.04.5安装:cuda driver + cuda toolkit

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

开盘啦 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 视为一个包…

电商ERP管理系统,Java+Vue,含源码与文档,统筹订单、库存等,助力电商企业高效运营

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

Spring Boot微服务架构(四):微服务的划分原则

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

【打卡】树状数组的操作

#define MAXN 1000 int n; // 数组实际长度 int array[MAXN]; // 原始数组(下标从0开始) int tree[MAXN]; // 树状数组(下标从1开始) int p[MAXN]; // 前缀和数组(下标从1…

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. 总结 前言 本文讲…

模拟电子技术基础----绪论

一、电子技术的发展 1.电子技术的发展,推动计算机技术的发展,使之“无孔不入”,应用广泛! •广播通信:发射机、接收机、扩音、录音、程控交换机、电话、手机 •网络:路由器、ATM交换机、收发器、调制解调…

iOS 使用 - 设置 来电震动/关闭震动

来电震动是一个很直观的老功能。但到了iOS 18,苹果却把震动功能的开关藏得越来越深,甚至分散在不同的菜单里,让用户难以找到。这里记录分享设置方法: 1. 震动开关的路径 设置 → 通用 → 辅助功能 → 触控 → 震动 2. 来电震动…

[C语言初阶]扫雷小游戏

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

谷歌medgemma-27b-text-it医疗大模型论文速读:多语言大型语言模型医学问答基准测试MedExpQA

《MedExpQA: 多语言大型语言模型医学问答基准测试》论文解析 一、引言 论文开篇指出大型语言模型(LLMs)在医学领域的巨大潜力,尤其是在医学问答(QA)方面。尽管LLMs在医学执照考试等场景中取得了令人瞩目的成绩&#…

DeepSeek+白果AI论文:开启答辩PPT生成的「智能双引擎」时代

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

SDC命令详解:使用set_logic_dc命令进行约束

相关阅读 SDC命令详解https://blog.csdn.net/weixin_45791458/category_12931432.html?spm1001.2014.3001.5482 set_logic_dc命令可以将当前设计中的输入端口为不关心(设置端口的driven_by_dont_care属性为true),该端口在综合是可以被认为是…

小程序涉及提供提供文本深度合成技术,请补充选择:深度合成-AI问答类目

一、问题描述 最近新项目AI咨询小程序审核上线,按照之前小程序的流程,之前审核,提示审核不通过,审核不通过的原因:小程序涉及提供提供文本深度合成技术 (如: AI问答) 等相关服务,请补充选择:深…