如何在 fastapi 中定义具有多个 api 密钥的自定义安全性?

woo*_*ody 5 fastapi

这是我的计划,但无法生成到 OpenAPI 文档的身份验证部分。

class HMACModel(BaseModel):
    api_key: APIKey = APIKey(**{"in": APIKeyIn.header}, name='Api-Key')
    signature: APIKey = APIKey(**{"in": APIKeyIn.header}, name='Signature')


class HMACAuth(APIKeyBase):
    model = HMACModel()
    scheme_name = 'HMACAuth'

    async def __call__(self, request: Request):
        api_key = request.headers.get('Api-Key')
        signature = request.headers.get('Signature')
        print(f's:{signature}, k:{api_key}')
        do_some_check()
        return api_key

@app.get('/')
async def test_api(req: ReqModel, api_key=Depends(HMACAuth())):
        pass
Run Code Online (Sandbox Code Playgroud)

当OpenAPI对象初始化并带有分析输出时,似乎自定义模型将被忽略OpenAPI(**output)。(输出中有安全模式,但OpenAPI对象中有missd)。

(参考代码:https://github.com/tiangolo/fastapi/blob/d60dd1b60e0acd0afcd5688e5759f450b6e7340c/fastapi/openapi/utils.py#L372)

小智 1

目前,让 OpenAPI 和多 api-key 依赖关系良好地发挥作用似乎是一个突出的问题,如本期所示

有几种方法可以处理它,其中最简单的方法是创建两个APIKeyHeader依赖项并在每个请求中使用它们。您实际上不需要从该类进行任何继承APIKeyBase,因为APIKeyHeaderFastAPI 提供的类应该自行完成该任务。

我还可能定义一个附加依赖项,将其他两个依赖项用作子依赖项,如下所示:

from typing import Optional

from fastapi import FastAPI, Depends, Security, HTTPException, status
from fastapi.security.api_key import APIKeyHeader


app = FastAPI()


class HMACAuth:
    async def __call__(
        self,
        x_hmac_api_key: str = Depends(APIKeyHeader(name="x-hmac-api-key", scheme_name="HMACApiKey")),
        x_hmac_signature: str = Depends(APIKeyHeader(name="x-hmac-signature", scheme_name="HMACSignature")),
    ) -> Optional[str]:
        if x_hmac_api_key and x_hmac_signature:
            # do whatever check is needed here
            if x_hmac_api_key in ["valid-api-key"] and x_hmac_signature in ["valid-signature"]:
                return x_hmac_api_key

        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Not authenticated",
        )


@app.get(
    "/", 
    response_model=str, 
    name="app:test-api",
    dependencies=[Security(HMACAuth())],
)
async def test_api() -> str:
     return "success"

Run Code Online (Sandbox Code Playgroud)

然后,您需要重写应用程序的openapi方法,如文档的扩展 OpenAPI部分中所述。

原因是具有此依赖项的任何路由都会为每个子依赖项生成不同的 OpenAPI 安全模式。这意味着用户可以在 OpenAPI 文档中输入任一值并进行“身份验证”。

为了解决这个问题,你可以这样做:

from fastapi.openapi.utils import get_openapi


# ...previous code


def merge_hmac_securities(list_of_securities: List[Dict[str, List]]) -> List[dict]:
    """
    Find HMACApiKey and HMACSignature securities and merge them
    """
    original_securities = []
    hmac_security = {}
    for security in list_of_securities:
        if "HMACApiKey" in security or "HMACSignature" in security:
            hmac_security.update(security)
        else:
            original_securities.append(security)

    return original_securities + ([hmac_security] if hmac_security else [])


def custom_openapi():
    if app.openapi_schema:
        return app.openapi_schema

    openapi_schema = get_openapi(
        title="Custom title",
        version="3.0.0",
        description="This is a very custom OpenAPI schema",
        routes=app.routes,
    )

    for path in openapi_schema.get("paths"):
        for http_method in openapi_schema["paths"][path]:
            list_of_securities = openapi_schema["paths"][path][http_method].get("security")
            if list_of_securities:
                merged_list_of_securities = merge_hmac_securities(list_of_securities)
                openapi_schema["paths"][path][http_method]["security"] = merged_list_of_securities

    app.openapi_schema = openapi_schema

    return app.openapi_schema


app.openapi = custom_openapi

Run Code Online (Sandbox Code Playgroud)

这样做应该可以解决您的两个问题。