You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

FastAPI WebSocket集成HTTPBasic认证报错:缺少request参数

问题:FastAPI WebSocket添加HTTPBasic认证后连接失败

使用FastAPI开发带HTTPBasic认证的简易WebSocket应用时,/{id}的HTTP请求可正常通过认证,但/ws/{client_id}的WebSocket连接失败,报错信息如下:

ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "D:\Installations\envs\chat-app\lib\site-packages\uvicorn\protocols\websockets\websockets_impl.py", line 240, in run_asgi
    result = await self.app(self.scope, self.asgi_receive, self.asgi_send)
  File "D:\Installations\envs\chat-app\lib\site-packages\uvicorn\middleware\proxy_headers.py", line 69, in __call__
    return await self.app(scope, receive, send)
  File "D:\Installations\envs\chat-app\lib\site-packages\fastapi\applications.py", line 1054, in __call__
    await super().__call__(scope, receive, send)
  File "D:\Installations\envs\chat-app\lib\site-packages\starlette\applications.py", line 123, in __call__
    await self.middleware_stack(scope, receive, send)
  File "D:\Installations\envs\chat-app\lib\site-packages\starlette\middleware\errors.py", line 151, in __call__
    await self.app(scope, receive, send)
  File "D:\Installations\envs\chat-app\lib\site-packages\starlette\middleware\exceptions.py", line 65, in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
  File "D:\Installations\envs\chat-app\lib\site-packages\starlette\_exception_handler.py", line 64, in wrapped_app
    raise exc
  File "D:\Installations\envs\chat-app\lib\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app
    await app(scope, receive, sender)
  File "D:\Installations\envs\chat-app\lib\site-packages\starlette\routing.py", line 756, in __call__
    await self.middleware_stack(scope, receive, send)
  File "D:\Installations\envs\chat-app\lib\site-packages\starlette\routing.py", line 776, in app
    await route.handle(scope, receive, send)
  File "D:\Installations\envs\chat-app\lib\site-packages\starlette\routing.py", line 373, in handle
    await self.app(scope, receive, send)
  File "D:\Installations\envs\chat-app\lib\site-packages\starlette\routing.py", line 96, in app
    await wrap_app_handling_exceptions(app, session)(scope, receive, send)
  File "D:\Installations\envs\chat-app\lib\site-packages\starlette\_exception_handler.py", line 64, in wrapped_app
    raise exc
  File "D:\Installations\envs\chat-app\lib\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app
    await app(scope, receive, sender)
  File "D:\Installations\envs\chat-app\lib\site-packages\starlette\routing.py", line 94, in app
    await func(session)
  File "D:\Installations\envs\chat-app\lib\site-packages\fastapi\routing.py", line 338, in app
    solved_result = await solve_dependencies(
  File "D:\Installations\envs\chat-app\lib\site-packages\fastapi\dependencies\utils.py", line 572, in solve_dependencies
    solved_result = await solve_dependencies(
  File "D:\Installations\envs\chat-app\lib\site-packages\fastapi\dependencies\utils.py", line 600, in solve_dependencies
    solved = await call(**sub_values)
TypeError: HTTPBasic.__call__() missing 1 required positional argument: 'request'
INFO:     connection open
INFO:     connection closed

原代码

from fastapi import FastAPI, HTTPException, status, Depends, Request, WebSocket, WebSocketDisconnect
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from fastapi.templating import Jinja2Templates
from typing import Annotated, List

app = FastAPI()
security = HTTPBasic()
templates = Jinja2Templates(directory="templates")


class ConnectionManager:
    def __init__(self):
        self.active_connections: List[WebSocket] = []

    async def connect(self, websocket: WebSocket):
        await websocket.accept()
        self.active_connections.append(websocket)

    def disconnect(self, websocket: WebSocket):
        self.active_connections.remove(websocket)

    async def send_personal_message(self, message: str, websocket: WebSocket):
        await websocket.send_text(message)

    async def broadcast(self, message: str, websocket: WebSocket) :
        for connection in self.active_connections:
            if (connection == websocket):
                continue
            await connection.send_text(message)

connectionmanager = ConnectionManager()

def validate_user(username: bytes, password: bytes):
    # 自定义用户验证逻辑
    return username == b"test", password == b"test"

def get_current_username(
    credentials: Annotated[HTTPBasicCredentials, Depends(security)],
):
    is_correct_username, is_correct_password= validate_user(credentials.username.encode("utf8"),credentials.password.encode("utf8"))
    
    if not (is_correct_username and is_correct_password):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect username or password",
            headers={"WWW-Authenticate": "Basic"},
        )
    return credentials.username

@app.get("/{id}", response_class=HTMLResponse)
def read_user(id: int,  username: Annotated[str, Depends(get_current_username)], request: Request):
    return templates.TemplateResponse("index.html", {"request": request})


@app.websocket("/ws/{client_id}")
async def websocket_endpoint(
        websocket: WebSocket, client_id: int, username: Annotated[str, Depends(get_current_username)], request: Request):
    await connectionmanager.connect(websocket)
    try:
        while True:
            data = await websocket.receive_text()
            await connectionmanager.send_personal_message(f"You : {data}", websocket)
            await connectionmanager.broadcast(f"Client #{client_id}: {data}", websocket)

    except WebSocketDisconnect:
        connectionmanager.disconnect(websocket)
        await connectionmanager.broadcast(f"Client #{client_id} left the chat")

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host='localhost', port=8000)
解决方案

错误根源是:HTTPBasic依赖是为HTTP请求设计的,它默认需要注入Request对象,但WebSocket的ASGI Scope和HTTP请求结构不同,无法自动注入Request,导致调用HTTPBasic.__call__时缺少必填参数。

解决方法是手动从WebSocket的Scope中提取认证信息,替代原有的HTTPBasic依赖:

1. 编写WebSocket专属的认证依赖

from base64 import b64decode

def get_current_username_from_websocket(websocket: WebSocket):
    # 从WebSocket headers中提取Authorization头
    auth_header = None
    for key, value in websocket.scope["headers"]:
        if key == b"authorization":
            auth_header = value.decode()
            break
    
    if not auth_header:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Missing credentials",
            headers={"WWW-Authenticate": "Basic"},
        )
    
    # 解析Basic认证格式
    auth_parts = auth_header.split(" ", 1)
    if len(auth_parts) != 2 or auth_parts[0].lower() != "basic":
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Unsupported authentication method",
            headers={"WWW-Authenticate": "Basic"},
        )
    
    try:
        decoded_creds = b64decode(auth_parts[1]).decode("utf-8")
        username, password = decoded_creds.split(":", 1)
    except (ValueError, UnicodeDecodeError):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid credentials format",
            headers={"WWW-Authenticate": "Basic"},
        )
    
    # 复用原有用户验证逻辑
    is_correct_username, is_correct_password = validate_user(username.encode("utf8"), password.encode("utf8"))
    if not (is_correct_username and is_correct_password):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect username or password",
            headers={"WWW-Authenticate": "Basic"},
        )
    
    return username

2. 修改WebSocket路由

替换依赖为上面的函数,同时移除不需要的Request参数:

@app.websocket("/ws/{client_id}")
async def websocket_endpoint(
        websocket: WebSocket, client_id: int, 
        username: Annotated[str, Depends(get_current_username_from_websocket)]):
    # 先完成认证,再接受连接
    await connectionmanager.connect(websocket)
    try:
        while True:
            data = await websocket.receive_text()
            await connectionmanager.send_personal_message(f"You : {data}", websocket)
            await connectionmanager.broadcast(f"Client #{client_id}: {data}", websocket)

    except WebSocketDisconnect:
        connectionmanager.disconnect(websocket)
        await connectionmanager.broadcast(f"Client #{client_id} left the chat")

3. 客户端连接说明

WebSocket客户端在发起连接时,需要在握手请求中携带Authorization: Basic <base64编码的用户名:密码>头,否则会被拒绝连接。

原有的HTTP路由/{id}可以继续使用get_current_username依赖,不受影响。


内容的提问来源于stack exchange,提问作者Aravindan vaithialingam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 03:05:56