阅读(2771) (1)

httpx 自定义身份验证

2022-07-20 13:57:35 更新

发出请求或实例化​Client​时,​auth​参数可用于传递要使用的身份验证方案。​auth​参数可能是以下之一...

  • username​/​password​的双元组,用于基本身份验证。
  • httpx.BasicAuth()​或​httpx.DigestAuth()​ 的实例。
  • 可调用的,接受请求并返回经过身份验证的请求实例。
  • httpx.Auth​的子类的实例。

其中涉及最多的是最后一个,它允许您创建涉及一个或多个请求的身份验证流。​httpx.Auth​的子类应该实现 ​def auth_flow(request)​ ,并生成需要发出的任何请求...

class MyCustomAuth(httpx.Auth):
    def __init__(self, token):
        self.token = token

    def auth_flow(self, request):
        # Send the request, with a custom `X-Authentication` header.
        request.headers['X-Authentication'] = self.token
        yield request

如果身份验证流需要多个请求,您可以发出多个​yield​,并在每种情况下获取响应...

class MyCustomAuth(httpx.Auth):
    def __init__(self, token):
        self.token = token

    def auth_flow(self, request):
      response = yield request
      if response.status_code == 401:
          # If the server issues a 401 response then resend the request,
          # with a custom `X-Authentication` header.
          request.headers['X-Authentication'] = self.token
          yield request

自定义身份验证类设计为不执行任何 I/O,以便它们可以同时用于同步和异步​Client​实例。如果要实现需要请求正文的身份验证方案,则需要使用​requires_request_body​属性在类上指示这一点。

然后,您将能够在​.auth_flow()​方法中访问​request.content​。

class MyCustomAuth(httpx.Auth):
    requires_request_body = True

    def __init__(self, token):
        self.token = token

    def auth_flow(self, request):
      response = yield request
      if response.status_code == 401:
          # If the server issues a 401 response then resend the request,
          # with a custom `X-Authentication` header.
          request.headers['X-Authentication'] = self.sign_request(...)
          yield request

    def sign_request(self, request):
        # Create a request signature, based on `request.method`, `request.url`,
        # `request.headers`, and `request.content`.
        ...

同样,如果要实现需要访问响应正文的方案,请使用​requires_response_body​属性。然后,您将能够访问响应正文属性和方法,例如​response.content​ 、​response.text ​、​response.json()​ 等。

class MyCustomAuth(httpx.Auth):
    requires_response_body = True

    def __init__(self, access_token, refresh_token, refresh_url):
        self.access_token = access_token
        self.refresh_token = refresh_token
        self.refresh_url = refresh_url

    def auth_flow(self, request):
        request.headers["X-Authentication"] = self.access_token
        response = yield request

        if response.status_code == 401:
            # If the server issues a 401 response, then issue a request to
            # refresh tokens, and resend the request.
            refresh_response = yield self.build_refresh_request()
            self.update_tokens(refresh_response)

            request.headers["X-Authentication"] = self.access_token
            yield request

    def build_refresh_request(self):
        # Return an `httpx.Request` for refreshing tokens.
        ...

    def update_tokens(self, response):
        # Update the `.access_token` and `.refresh_token` tokens
        # based on a refresh response.
        data = response.json()
        ...

如果确实需要执行 HTTP 请求以外的 I/O(如访问基于磁盘的缓存),或者需要使用并发基元(如锁),则应覆盖 ​sync_auth_flow()​.和 ​async_auth_flow()​(而不是​auth_flow() ​)。前者将由​httpx.Client ​使用,而后者将由​httpx.AsyncClient ​使用。

import asyncio
import threading
import httpx


class MyCustomAuth(httpx.Auth):
    def __init__(self):
        self._sync_lock = threading.RLock()
        self._async_lock = asyncio.Lock()

    def sync_get_token(self):
        with self._sync_lock:
            ...

    def sync_auth_flow(self, request):
        token = self.sync_get_token()
        request.headers["Authorization"] = f"Token {token}"
        yield request

    async def async_get_token(self):
        async with self._async_lock:
            ...

    async def async_auth_flow(self, request):
        token = await self.async_get_token()
        request.headers["Authorization"] = f"Token {token}"
        yield request

如果您只想支持这两种方法之一,则仍应重写它,但应提出显式 ​RuntimeError​。

import httpx
import sync_only_library


class MyCustomAuth(httpx.Auth):
    def sync_auth_flow(self, request):
        token = sync_only_library.get_token(...)
        request.headers["Authorization"] = f"Token {token}"
        yield request

    async def async_auth_flow(self, request):
        raise RuntimeError("Cannot use a sync authentication class with httpx.AsyncClient")