Python Request源码解读之auth.py

发布时间:2024年01月02日

Digest认证是一种基于摘要算法的认证方式,使用时需要先发送一个请求,服务端返回一段Token,客户端需要使用这个Token进行加密后再发送请求。Python中的Request库同样可以通过设置auth参数实现Digest认证。

# -*- coding: utf-8 -*-

"""
requests.auth
~~~~~~~~~~~~~

This module contains the authentication handlers for Requests.
"""

import os
import re
import time
import hashlib
import threading
import warnings

from base64 import b64encode

from .compat import urlparse, str, basestring
from .cookies import extract_cookies_to_jar
from ._internal_utils import to_native_string
from .utils import parse_dict_header

CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded'
CONTENT_TYPE_MULTI_PART = 'multipart/form-data'


def _basic_auth_str(username, password):
    """Returns a Basic Auth string."""

    # "I want us to put a big-ol' comment on top of it that
    # says that this behaviour is dumb but we need to preserve
    # it because people are relying on it."
    #    - Lukasa
    #
    # These are here solely to maintain backwards compatibility
    # for things like ints. This will be removed in 3.0.0.
    if not isinstance(username, basestring):
        warnings.warn(
            "Non-string usernames will no longer be supported in Requests "
            "3.0.0. Please convert the object you've passed in ({!r}) to "
            "a string or bytes object in the near future to avoid "
            "problems.".format(username),
            category=DeprecationWarning,
        )
        username = str(username)

    if not isinstance(password, basestring):
        warnings.warn(
            "Non-string passwords will no longer be supported in Requests "
            "3.0.0. Please convert the object you've passed in ({!r}) to "
            "a string or bytes object in the near future to avoid "
            "problems.".format(password),
            category=DeprecationWarning,
        )
        password = str(password)
    # -- End Removal --

    if isinstance(username, str):
        username = username.encode('latin1')

    if isinstance(password, str):
        password = password.encode('latin1')

    authstr = 'Basic ' + to_native_string(
        b64encode(b':'.join((username, password))).strip()
    )

    return authstr
#这段代码定义了一个名为 _basic_auth_str 的函数,用于生成一个基本的认证字符串,通常用于 HTTP Basic 认证。下面是对代码的详细解析:

#函数接受两个参数:username 和 password,分别代表用户名和密码。
#函数开始时有一段注释,说明这个函数是为了保持向后兼容性而存在的,因为在未来的版本中,这种行为可能会被视为不智。
#如果 username 不是字符串类型,函数会发出一个弃用警告,并提示用户在不久的将来将其转换为字符串或字节对象,以避免问题。然后,将 username 转换为字符串类型。
#如果 password 不是字符串类型,函数同样会发出一个弃用警告,并提示用户在不久的将来将其转换为字符串或字节对象,以避免问题。然后,将 password 转换为字符串类型。
#接下来,如果 username 和 password 是字符串类型,它们会被编码为 'latin1' 格式。这是为了确保它们可以正确地被 base64 编码。
#然后,使用 base64 对用户名和密码进行编码,并用冒号分隔它们。这是为了生成一个符合 HTTP Basic 认证格式的字符串。
#最后,将编码后的字符串添加到 'Basic ' 前缀,然后返回这个认证字符串。
#整个函数的目的是为了生成一个符合 HTTP Basic 认证格式的字符串,同时确保用户名和密码都是字符串类型,并在必要时发出弃用警告。 
#该方法在adapt.py被引用,验证用户名密码的类型

class AuthBase(object):
    """Base class that all auth implementations derive from"""

    def __call__(self, r):
        raise NotImplementedError('Auth hooks must be callable.')
#这是一个简单的Python类定义,用于表示一个认证基类(AuthBase)。以下是对这段代码的详细解释:

#class AuthBase(object):

#这行代码定义了一个新的类,名为AuthBase。它继承自object,这是所有新式类的基类。在Python 3中,所有类都自动继承自object,所以这里的显式继承不是必需的,但为了清晰和向后兼容性,它仍然被包含。
#"""Base class that all auth implementations derive from"""

#这是一个多行字符串,用作类的文档字符串(或称为docstring)。它描述了这个类的用途。这里说明这个类是所有认证实现所派生的基础类。
#def __call__(self, r):

#这是类的特殊方法__call__的定义。当一个类的实例被当作函数调用时(即你尝试像函数那样使用它),这个方法会被自动调用。这里,它接受一个参数r。
raise NotImplementedError('Auth hooks must be callable.')

#这行代码引发一个NotImplementedError异常。当其他代码尝试像函数那样调用这个类的实例时,这会阻止它这样做并显示给定的错误消息。这个异常通常用于指示子类必须实现某个方法或功能。在这里,它表示“Auth hooks must be callable”,意味着所有的认证钩子(auth hooks)必须是可调用的。
#总的来说,这个AuthBase类是一个基类,用于定义认证逻辑。任何从这个类派生的子类都应该实现自己的认证逻辑,这意味着它们需要提供自己的__call__方法。如果子类没有这样做,当尝试像函数一样使用它时,会引发一个错误。   

class HTTPBasicAuth(AuthBase):
    """Attaches HTTP Basic Authentication to the given Request object."""

    def __init__(self, username, password):
        self.username = username
        self.password = password

    def __eq__(self, other):
        return all([
            self.username == getattr(other, 'username', None),
            self.password == getattr(other, 'password', None)
        ])

    def __ne__(self, other):
        return not self == other

    def __call__(self, r):
        r.headers['Authorization'] = _basic_auth_str(self.username, self.password)
        return r
#这是一个Python类,用于为HTTP请求添加基本认证(HTTP Basic Authentication)。下面是对代码的详细解释:

#class HTTPBasicAuth(AuthBase):

#这行代码定义了一个新的类,名为HTTPBasicAuth,它继承自AuthBase。这意味着HTTPBasicAuth类可以使用AuthBase类中的方法和属性。
#"""Attaches HTTP Basic Authentication to the given Request object."""

#这是一个多行字符串,用作类的文档字符串(或称为docstring)。它描述了这个类的用途。这里说明这个类的目的是将HTTP基本认证附加到给定的请求对象上。
#def __init__(self, username, password):

#这是类的初始化方法,它接受两个参数:username和password。这些参数用于存储用户名和密码,以便在后续的请求中使用。
#self.username = username 和 self.password = password

#这两行代码将传递给初始化方法的用户名和密码分别存储在实例的username和password属性中。
#def __eq__(self, other):

#这是类的特殊方法,用于比较两个对象是否相等。当使用“==”运算符比较两个HTTPBasicAuth对象时,这个方法会被调用。
#return all([...])

#这行代码检查两个对象是否相等,通过比较它们的用户名和密码属性。如果两个对象的用户名和密码都相同,则它们被认为是相等的。
#def __ne__(self, other):

#这是类的特殊方法,用于比较两个对象是否不相等。当使用“!=”运算符比较两个HTTPBasicAuth对象时,这个方法会被调用。它简单地返回两个对象是否不相等的结果。
#def __call__(self, r):

#这是类的特殊方法,允许类的实例像函数一样被调用。这里,它接受一个参数r,通常是一个请求对象。
#r.headers['Authorization'] = _basic_auth_str(self.username, self.password)

#这行代码将基本认证头添加到请求对象的头部中。它使用一个未在代码中定义的函数_basic_auth_str来生成基本认证字符串,该字符串包含用户名和密码。
#return r
#这行代码返回修改后的请求对象。这样,调用这个类的实例就像调用一个函数一样,它会自动添加基本认证头到请求中。
#总的来说,这个类允许你为HTTP请求添加基本认证头,通过提供用户名和密码作为参数来创建一个实例,然后像函数一样调用该实例来自动添加认证头到请求中。

class HTTPProxyAuth(HTTPBasicAuth):
    """Attaches HTTP Proxy Authentication to a given Request object."""

    def __call__(self, r):
        r.headers['Proxy-Authorization'] = _basic_auth_str(self.username, self.password)
        return r
#这段代码定义了一个名为HTTPProxyAuth的类,该类继承自HTTPBasicAuth。这个新类是为了附加HTTP代理认证到给定的请求对象。以下是对代码的详细解释:

#class HTTPProxyAuth(HTTPBasicAuth):

#这行代码定义了一个新的类,名为HTTPProxyAuth,它继承自HTTPBasicAuth。这意味着HTTPProxyAuth类可#以使用HTTPBasicAuth类中的方法和属性。

#def __call__(self, r):

#这是类的特殊方法,允许类的实例像函数一样被调用。这里,它接受一个参数r,通常是一个请求对象。
#r.headers['Proxy-Authorization'] = _basic_auth_str(self.username, self.password)

#这行代码将代理认证头添加到请求对象的头部中。它使用一个未在代码中定义的函数_basic_auth_str来生成基本认证字符串,该字符串包含用户名和密码。与HTTPBasicAuth不同,这里使用的是Proxy-Authorization头而不是Authorization头。
#return r
#这行代码返回修改后的请求对象。这样,调用这个类的实例就像调用一个函数一样,它会自动添加代理认证头到请求中。
#总的来说,这个类允许你为HTTP请求添加代理认证头,通过提供用户名和密码作为参数来创建一个实例,然后像函数一样调用该实例来自动添加认证头到请求中。与HTTPBasicAuth类不同,它使用的是Proxy-Authorization头而不是Authorization头。        
        
class HTTPDigestAuth(AuthBase):
    """Attaches HTTP Digest Authentication to the given Request object."""

    def __init__(self, username, password):
        self.username = username
        self.password = password
        # Keep state in per-thread local storage
        self._thread_local = threading.local()

    def init_per_thread_state(self):
        # Ensure state is initialized just once per-thread
        if not hasattr(self._thread_local, 'init'):
            self._thread_local.init = True
            self._thread_local.last_nonce = ''
            self._thread_local.nonce_count = 0
            self._thread_local.chal = {}
            self._thread_local.pos = None
            self._thread_local.num_401_calls = None

#这段代码定义了一个名为HTTPDigestAuth的类,该类继承自AuthBase。这个类是为了附加HTTP摘要认证到给定的请求对象。以下是对代码的详细解释:

#class HTTPDigestAuth(AuthBase):

#这行代码定义了一个新的类,名为HTTPDigestAuth,它继承自AuthBase。这意味着HTTPDigestAuth类可以使用AuthBase类中的方法和属性。
#"""Attaches HTTP Digest Authentication to the given Request object."""

#这是一个多行字符串,用作类的文档字符串(或称为docstring)。它描述了这个类的用途。这里说明这个类的目的是将HTTP摘要认证附加到给定的请求对象上。
#def __init__(self, username, password):

#这是类的初始化方法,它接受两个参数:username和password。这些参数用于存储用户名和密码,以便在后续的请求中使用。
#self.username = username 和 self.password = password

#这两行代码将传递给初始化方法的用户名和密码分别存储在实例的username和password属性中。
# Keep state in per-thread local storage

#这是一条注释,说明该类使用线程本地存储来维护状态。这意味着每个线程都有其自己的状态数据,互不干扰。
#self._thread_local = threading.local()

#这行代码创建一个线程局部对象,用于存储每个线程的特定状态。这样,每个线程都可以有自己的认证状态,不会与其他线程共享。
#def init_per_thread_state(self):
#这是一个方法,用于初始化线程局部状态。由于状态存储在每个线程中,因此需要确保每个线程的状态都正确初始化。
#接下来的代码块检查线程局部对象是否已经具有某些属性,如果没有,则初始化它们。这些属性包括:
#last_nonce: 用于记录最后一个nonce值。
#nonce_count: 用于跟踪nonce计数器。
#chal: 用于存储挑战信息。
#pos: 用于跟踪请求的位置或进度。
#num_401_calls: 用于跟踪返回401响应的次数。这些属性用于HTTP摘要认证的逻辑,确保每个线程都有自己的认证状态。
#总的来说,这个类提供了HTTP摘要认证的功能,它使用线程本地存储来维护每个线程的认证状态。通过提供用户名和密码作为参数来创建一个实例,然后可以使用该实例来自动添加摘要认证头到请求中。
def build_digest_header(self, method, url):
        """
        :rtype: str
        """

        realm = self._thread_local.chal['realm']
        nonce = self._thread_local.chal['nonce']
        qop = self._thread_local.chal.get('qop')
        algorithm = self._thread_local.chal.get('algorithm')
        opaque = self._thread_local.chal.get('opaque')
        hash_utf8 = None

        if algorithm is None:
            _algorithm = 'MD5'
        else:
            _algorithm = algorithm.upper()
        # lambdas assume digest modules are imported at the top level
        if _algorithm == 'MD5' or _algorithm == 'MD5-SESS':
            def md5_utf8(x):
                if isinstance(x, str):
                    x = x.encode('utf-8')
                return hashlib.md5(x).hexdigest()
            hash_utf8 = md5_utf8
        elif _algorithm == 'SHA':
            def sha_utf8(x):
                if isinstance(x, str):
                    x = x.encode('utf-8')
                return hashlib.sha1(x).hexdigest()
            hash_utf8 = sha_utf8
        elif _algorithm == 'SHA-256':
            def sha256_utf8(x):
                if isinstance(x, str):
                    x = x.encode('utf-8')
                return hashlib.sha256(x).hexdigest()
            hash_utf8 = sha256_utf8
        elif _algorithm == 'SHA-512':
            def sha512_utf8(x):
                if isinstance(x, str):
                    x = x.encode('utf-8')
                return hashlib.sha512(x).hexdigest()
            hash_utf8 = sha512_utf8

        KD = lambda s, d: hash_utf8("%s:%s" % (s, d))

        if hash_utf8 is None:
            return None

        # XXX not implemented yet
        entdig = None
        p_parsed = urlparse(url)
        #: path is request-uri defined in RFC 2616 which should not be empty
        path = p_parsed.path or "/"
        if p_parsed.query:
            path += '?' + p_parsed.query

        A1 = '%s:%s:%s' % (self.username, realm, self.password)
        A2 = '%s:%s' % (method, path)

        HA1 = hash_utf8(A1)
        HA2 = hash_utf8(A2)

        if nonce == self._thread_local.last_nonce:
            self._thread_local.nonce_count += 1
        else:
            self._thread_local.nonce_count = 1
        ncvalue = '%08x' % self._thread_local.nonce_count
        s = str(self._thread_local.nonce_count).encode('utf-8')
        s += nonce.encode('utf-8')
        s += time.ctime().encode('utf-8')
        s += os.urandom(8)

        cnonce = (hashlib.sha1(s).hexdigest()[:16])
        if _algorithm == 'MD5-SESS':
            HA1 = hash_utf8('%s:%s:%s' % (HA1, nonce, cnonce))

        if not qop:
            respdig = KD(HA1, "%s:%s" % (nonce, HA2))
        elif qop == 'auth' or 'auth' in qop.split(','):
            noncebit = "%s:%s:%s:%s:%s" % (
                nonce, ncvalue, cnonce, 'auth', HA2
            )
            respdig = KD(HA1, noncebit)
        else:
            # XXX handle auth-int.
            return None

        self._thread_local.last_nonce = nonce

        # XXX should the partial digests be encoded too?
        base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \
               'response="%s"' % (self.username, realm, nonce, path, respdig)
        if opaque:
            base += ', opaque="%s"' % opaque
        if algorithm:
            base += ', algorithm="%s"' % algorithm
        if entdig:
            base += ', digest="%s"' % entdig
        if qop:
            base += ', qop="auth", nc=%s, cnonce="%s"' % (ncvalue, cnonce)

        return 'Digest %s' % (base)

'''这段代码是一个Python函数,用于构建HTTP摘要认证头。HTTP摘要认证是一种基于挑战-响应的认证机制,用于在HTTP协议中提供身份验证和数据完整性。下面是对代码的逐行解释:

#def build_digest_header(self, method, url):

#定义一个名为build_digest_header的函数,它接受三个参数:self、method和url。
"""

开始一个多行字符串,用作函数的文档字符串(或称为docstring)。
:rtype: str

在docstring中指定函数的返回类型为字符串。
"""

结束docstring。
realm = self._thread_local.chal['realm']

从线程局部存储中获取摘要认证的领域(realm)值。
nonce = self._thread_local.chal['nonce']

从线程局部存储中获取摘要认证的随机数(nonce)值。
qop = self._thread_local.chal.get('qop')

从线程局部存储中获取摘要认证的QOP(Quality of Protection)值。QOP决定了摘要认证的详细程度。
algorithm = self._thread_local.chal.get('algorithm')

从线程局部存储中获取摘要认证所使用的算法。
opaque = self._thread_local.chal.get('opaque')

从线程局部存储中获取摘要认证的Opaque值。Opaque通常用于验证服务器的完整性。
hash_utf8 = None

初始化一个变量hash_utf8为None,用于存储摘要函数。
if algorithm is None:
检查是否指定了摘要算法。
_algorithm = 'MD5'
如果未指定算法,则默认使用MD5算法。
else:
否则,如果指定了算法。
_algorithm = algorithm.upper()
将算法转换为大写。这样确保算法是统一的大小写,便于后续处理。
# lambdas assume digest modules are imported at the top level
这是一个注释,说明下面的代码依赖于摘要模块已经被导入。
16-27. 这些代码块根据指定的摘要算法(如MD5、SHA、SHA-256或SHA-512)定义不同的函数来计算摘要。这些函数都是使用lambda表达式定义的匿名函数,并且使用UTF-8编码来处理字符串输入。
KD = lambda s, d: hash_utf8("%s:%s" % (s, d))
定义一个lambda函数KD,它接受两个参数s和d,并使用之前定义的hash_utf8函数来计算它们的摘要。这个KD函数用于后续计算请求和响应的摘要。
if hash_utf8 is None:
检查是否成功定义了摘要函数(即hash_utf8是否为None)。如果没有定义成功,说明没有指定合适的摘要算法或缺少必要的模块,函数将返回None。
30-35. 这些代码行尚未实现,用“XXX not implemented yet”注释标明。这些代码可能用于处理某些边缘情况或实现其他功能。
entdig = None
初始化一个变量entdig为None,但这个变量在后续代码中没有使用。可能是用于后续功能或备用的变量。
p_parsed = urlparse(url)
使用urllib的urlparse函数解析传入的URL,并将结果存储在变量p_parsed中。这用于提取URL的各个组成部分,如路径和查询字符串。
#: path is request-uri defined in RFC 2616 which should not be empty
这是一个注释,说明路径是RFC 2616中定义的请求URI,它不应该为空。这是为了确保路径的有效性。
path = p_parsed.path or "/"
从解析后的URL中提取路径部分,如果路径为空(即URL不包含路径),则将路径设置为根路径“/”。
if p_parsed.query:
检查解析后的URL是否包含查询字符串。如果包含查询字符串,则执行下一行代码;否则跳过这部分代码。
A1 = '%s:%s:%s' % (self.username, realm, self.password)

定义一个字符串A1,它由用户名、领域(realm)和密码组成,三者之间用冒号分隔。这是计算摘要所需的第一个字符串。
A2 = '%s:%s' % (method, path)

定义一个字符串A2,它由请求方法和请求URI组成,两者之间用冒号分隔。这是计算摘要所需的第二个字符串。
HA1 = hash_utf8(A1)

使用之前定义的hash_utf8函数计算A1的摘要,并将结果存储在变量HA1中。
HA2 = hash_utf8(A2)

使用hash_utf8函数计算A2的摘要,并将结果存储在变量HA2中。
接下来的几行代码处理非ce(nonce)计数器和非ce值的增加、存储以及与时间戳和随机数的组合:

如果当前请求的非ce与上一次请求的相同,非ce计数器增加1;否则,非ce计数器重置为1。
生成一个随机的cnonce(客户端非ce)。
if _algorithm == 'MD5-SESS':

检查所使用的摘要算法是否为MD5-SESS。如果是,则需要将HA1、非ce和cnonce组合后再进行摘要计算。
if not qop:

检查是否指定了qop(quality of protection)值。如果没有指定或指定为"auth",则进入对应的代码块;否则进入其他情况的代码块(目前为空)。
respdig = KD(HA1, "%s:%s" % (nonce, HA2))

使用KD函数计算响应的摘要,其中包含HA1、非ce和HA2。计算出的摘要存储在变量respdig中。
self._thread_local.last_nonce = nonce

将当前请求的非ce值设置为线程局部变量last_nonce,以便下次请求时使用。
接下来的几行代码构建完整的摘要认证头信息:

构建一个包含各种参数的字符串base,这些参数包括用户名、领域、非ce、请求URI、响应摘要等。根据是否指定了其他参数(如opaque、algorithm、entdig、qop),还会添加相应的字段。
最后,将base字符串作为Digest字符串的一部分返回。
总结:这段代码的主要目的是根据HTTP请求参数和服务器端的摘要认证参数来构建完整的摘要认证头信息。它首先计算请求和响应的摘要,然后根据这些摘要和其他参数来构建一个格式化的字符串作为摘要认证头的内容。这个头部信息将在后续的HTTP请求中发送给服务器,以证明客户端的身份并验证其授权
'''
def handle_redirect(self, r, **kwargs):
        """Reset num_401_calls counter on redirects."""
        if r.is_redirect:
            self._thread_local.num_401_calls = 1 代码释义
'''
handle_redirect,定义在一个类中(由于使用了self关键字,这是一个类的方法)。该方法的目的是处理HTTP重定向,并在遇到重定向时重置num_401_calls计数器。

r.is_redirect: 这个条件检查传入的响应对象r是否是一个重定向。通常,这会是一个布尔值,表示是否发生了重定向。
self._thread_local.num_401_calls = 1: 如果r.is_redirect为真(即发生了重定向),则将num_401_calls计数器重置为1。这里使用的是线程局部变量(由_thread_local表示),这意味着每个线程都有自己独立的变量副本。
'''
def handle_401(self, r, **kwargs):
        """
        Takes the given response and tries digest-auth, if needed.

        :rtype: requests.Response
        """

        # If response is not 4xx, do not auth
        # See https://github.com/requests/requests/issues/3772
        if not 400 <= r.status_code < 500:
            self._thread_local.num_401_calls = 1
            return r

        if self._thread_local.pos is not None:
            # Rewind the file position indicator of the body to where
            # it was to resend the request.
            r.request.body.seek(self._thread_local.pos)
        s_auth = r.headers.get('www-authenticate', '')

        if 'digest' in s_auth.lower() and self._thread_local.num_401_calls < 2:

            self._thread_local.num_401_calls += 1
            pat = re.compile(r'digest ', flags=re.IGNORECASE)
            self._thread_local.chal = parse_dict_header(pat.sub('', s_auth, count=1))

            # Consume content and release the original connection
            # to allow our new request to reuse the same one.
            r.content
            r.close()
            prep = r.request.copy()
            extract_cookies_to_jar(prep._cookies, r.request, r.raw)
            prep.prepare_cookies(prep._cookies)

            prep.headers['Authorization'] = self.build_digest_header(
                prep.method, prep.url)
            _r = r.connection.send(prep, **kwargs)
            _r.history.append(r)
            _r.request = prep

            return _r

        self._thread_local.num_401_calls = 1
        return r
'''
这段代码是一个处理HTTP重定向和401 Unauthorized响应的逻辑。具体来说,它处理了HTTP状态码为401的情况,特别是与Digest认证相关的部分。以下是代码的详细解释:

判断重定向状态码:
if not 400 <= r.status_code < 500:
    self._thread_local.num_401_calls = 1
    return r
如果HTTP响应的状态码不在400到499之间(即不是重定向),则将num_401_calls计数器设置为1,并直接返回响应。
2. 重置请求体位置:

if self._thread_local.pos is not None:
    r.request.body.seek(self._thread_local.pos)
如果存在一个之前保存的位置(self._thread_local.pos),则将请求体的位置重置到该位置。这是为了在重新发送请求时保持一致。
3. 处理www-authenticate头:

s_auth = r.headers.get('www-authenticate', '')
if 'digest' in s_auth.lower() and self._thread_local.num_401_calls < 2:
    ...
从响应头中获取www-authenticate字段,并检查其中是否包含'digest'。如果包含并且num_401_calls小于2,则执行以下操作:

增加num_401_calls计数。
使用正则表达式处理www-authenticate头,以提取Digest认证相关的参数。
消耗响应的内容并释放原始连接,以便新的请求可以重用相同的连接。
复制原始请求,并为其添加新的Cookie和Authorization头。
发送新的请求并获取响应。
重置计数器和返回:
self._thread_local.num_401_calls = 1
return r
如果上述条件不满足(即状态码不是401或www-authenticate头中没有'digest'),则将num_401_calls重置为1,并直接返回响应。

总结:这段代码的主要目的是处理HTTP的401 Unauthorized响应,特别是与Digest认证相关的部分。当服务器要求客户端进行认证时,代码会尝试自动构建和发送认证头,并可能重试一次(取决于num_401_calls的值)。
'''
def __call__(self, r):
        # Initialize per-thread state, if needed
        self.init_per_thread_state()
        # If we have a saved nonce, skip the 401
        if self._thread_local.last_nonce:
            r.headers['Authorization'] = self.build_digest_header(r.method, r.url)
        try:
            self._thread_local.pos = r.body.tell()
        except AttributeError:
            # In the case of HTTPDigestAuth being reused and the body of
            # the previous request was a file-like object, pos has the
            # file position of the previous body. Ensure it's set to
            # None.
            self._thread_local.pos = None
        r.register_hook('response', self.handle_401)
        r.register_hook('response', self.handle_redirect)
        self._thread_local.num_401_calls = 1

        return r
'''
这段代码定义了一个类的特殊方法__call__,它使得该类的实例可以像函数那样被调用。这个方法在HTTP请求被发送之前被调用,并对请求进行了一些处理。以下是对这段代码的详细解释:

初始化线程状态:
self.init_per_thread_state()
调用init_per_thread_state方法来初始化线程相关的状态。这个方法的具体实现没有在提供的代码中显示,但它很可能是用于初始化与当前线程相关的变量或设置。
2. 处理已保存的非ce:
if self._thread_local.last_nonce:
    r.headers['Authorization'] = self.build_digest_header(r.method, r.url)
如果线程局部变量last_nonce有值(也就是说,如果之前已经有一个非ce被保存下来),则构建一个Digest认证头并添加到请求的头部。
3. 保存请求体的当前位置:
try:
    self._thread_local.pos = r.body.tell()
except AttributeError:
    self._thread_local.pos = None
尝试获取请求体的当前位置并将其保存到线程局部变量pos中。如果请求体不是一个文件-like对象(例如,它可能是一个字符串或其他类型的响应体),则会引发AttributeError,此时将pos设置为None。
4. 注册处理钩子:
r.register_hook('response', self.handle_401)
r.register_hook('response', self.handle_redirect)
为请求注册两个处理钩子:一个用于处理401响应(handle_401),另一个用于处理重定向(handle_redirect)。这意味着当相应的HTTP响应返回时,这些方法将被自动调用。
5. 重置401调用计数:
self._thread_local.num_401_calls = 1
将线程局部变量num_401_calls重置为1,表示从当前请求开始,尚未发生过401响应。
6. 返回处理后的请求:
return r
最后,该方法返回处理后的请求对象。这样,原始的HTTP库或其他使用此类的代码可以继续处理请求。

总之,这段代码主要是对HTTP请求进行预处理,包括设置Digest认证头、保存请求体的位置以及注册处理特定HTTP响应的钩子。
'''
 def __eq__(self, other):
        return all([
            self.username == getattr(other, 'username', None),
            self.password == getattr(other, 'password', None)
        ])

    def __ne__(self, other):
        return not self == other
'''
这段代码定义了两个特殊方法:__eq__ 和 __ne__,用于比较两个对象是否相等和不相等。这两个方法通常用于自定义对象的比较操作。

__eq__ 方法:

参数:other - 另一个对象,用于与当前对象进行比较。
返回值:如果两个对象的 username 和 password 属性都相等,则返回 True,否则返回 False。
使用 getattr(other, 'username', None) 和 getattr(other, 'password', None) 的目的是为了确保即使 other 对象没有相应的属性也不会引发错误。如果 other 没有这些属性,那么它们会被视为 None,并且与 self 对象的相应属性进行比较。
__ne__ 方法:

参数:other - 另一个对象,用于与当前对象进行比较。
返回值:使用 not self == other 来检查两个对象是否不相等。换句话说,如果两个对象不相等,则返回 True;否则返回 False。
这两个方法通常用于自定义对象的比较操作,例如在集合中查找对象或使用比较运算符(如 == 和 !=)时。通过定义这些方法,你可以控制如何比较自定义对象的相等性。
'''

总结:
HTTPDigestAuth 是 Python 的一个第三方库,用于实现 HTTP Digest 认证。Digest 认证是一种基于挑战-响应的认证机制,用于在网络传输中提供身份验证和数据完整性保护。

HTTPDigestAuth 的主要作用是简化 HTTP Digest 认证的实现过程。通过使用 HTTPDigestAuth,你可以轻松地为 Python 的 HTTP 请求添加 Digest 认证功能。它提供了一组简单的函数和方法,使你能够生成和解析 Digest 认证的请求和响应。

以下是 HTTPDigestAuth 的主要功能:
自动处理 Digest 认证的请求和响应:
HTTPDigestAuth 会自动处理 Digest 认证的请求和响应,包括生成和解析挑战、计算响应摘要等。
支持多种身份验证方式:
HTTPDigestAuth 支持多种身份验证方式,如基本认证、摘要认证等,可以根据需要进行选择。
方便的 API:
HTTPDigestAuth 提供了一组简单易用的 API,使你能够轻松地添加 Digest 认证到 Python 的 HTTP 请求中。
支持多种 HTTP 库:
HTTPDigestAuth 可以与多种 Python 的 HTTP 库一起使用,如 requests、http.client 等。
总之,
HTTPDigestAuth 是一个方便的库,用于在 Python 中实现 HTTP Digest 认证。通过使用它,你可以轻松地为你的 HTTP 请求添加安全认证功能。

import requests
response = requests.get('http://httpbin.org/digest-auth/auth/user/passwd/MD5', auth=requests.auth.HTTPDigestAuth('user', 'passwd'))
上述代码中,使用get方法请求http://httpbin.org/digest-auth/auth/user/passwd/MD5接口,同时将HTTPDigestAuth对象通过auth参数的方式发送,如果认证成功,会返回一段JSON格式的数据。
文章来源:https://blog.csdn.net/qq_34399969/article/details/135332913
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。