FastAPI登录注册API在AWS API Gateway触发内部服务器错误求助
问题描述
登录与注册API在本地环境(http://127.0.0.1:8000/login)可正常响应,但部署到AWS Lambda+API Gateway后抛出异常;其他API均正常工作,仅登录注册接口出问题。移除try-except块会触发内部服务器错误,已配置CORS规则。
登录API参考代码:
@route.post("/login", tags=['Account']) def login(form_data: OAuth2PasswordRequestForm = Depends()): try: user = db.query(UserModel).filter_by(email=form_data.username).first() if user and verify_password(form_data.password, user.password): return {"access_token": signJWT(user), "token_type": "Bearer"} else: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect username or password") except: db.rollback() raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
CloudWatch中捕获的错误日志:
Traceback (most recent call last): File "/var/task/mangum/protocols/http.py", line 58, in run await app(self.scope, self.receive, self.send) File "/var/task/fastapi/applications.py", line 276, in __call__ await super().__call__(scope, receive, send) File "/var/task/starlette/applications.py", line 122, in __call__ await self.middleware_stack(scope, receive, send) File "/var/task/starlette/middleware/errors.py", line 184, in __call__ raise exc File "/var/task/starlette/middleware/errors.py", line 162, in __call__ await self.app(scope, receive, _send) File "/var/task/starlette/middleware/cors.py", line 83, in __call__ await self.app(scope, receive, send) File "/var/task/starlette/middleware/exceptions.py", line 79, in __call__ raise exc File "/var/task/starlette/middleware/exceptions.py", line 68, in __call__ await self.app(scope, receive, sender) File "/var/task/fastapi/middleware/asyncexitstack.py", line 21, in __call__ raise e File "/var/task/fastapi/middleware/asyncexitstack.py", line 18, in __call__ await self.app(scope, receive, send) File "/var/task/starlette/routing.py", line 718, in __call__ await route.handle(scope, receive, send) File "/var/task/starlette/routing.py", line 276, in handle await self.app(scope, receive, send) File "/var/task/starlette/routing.py", line 66, in app response = await func(request) File "/var/task/fastapi/routing.py", line 237, in app raw_response = await run_endpoint_function( File "/var/task/fastapi/routing.py", line 165, in run_endpoint_function return await run_in_threadpool(dependant.call, **values) File "/var/task/starlette/concurrency.py", line 41, in run_in_threadpool return await anyio.to_thread.run_sync(func, *args) File "/var/task/anyio/to_thread.py", line 33, in run_sync return await get_asynclib().run_sync_in_worker_thread( File "/var/task/anyio/_backends/_asyncio.py", line 877, in run_sync_in_worker_thread return await future File "/var/task/anyio/_backends/_asyncio.py", line 807, in run result = context.run(func, *args) File "/var/task/routes/users.py", line 28, in login if user and verify_password(form_data.password, user.password): File "/var/task/crud.py", line 15, in verify_password return pwd_context.verify(plain_password, hashed_password) File "/var/task/passlib/context.py", line 2347, in verify return record.verify(secret, hash, **kwds) File "/var/task/passlib/utils/handlers.py", line 792, in verify return consteq(self._calc_checksum(secret), chk) File "/var/task/passlib/handlers/bcrypt.py", line 591, in _calc_checksum self._stub_requires_backend() File "/var/task/passlib/utils/handlers.py", line 2254, in _stub_requires_backend cls.set_backend() File "/var/task/passlib/utils/handlers.py", line 2156, in set_backend return owner.set_backend(name, dryrun=dryrun) File "/var/task/passlib/utils/handlers.py", line 2176, in set_backend raise default_error passlib.exc.MissingBackendError: bcrypt: no backends available -- recommend you install one (e.g. 'pip install bcrypt') [ERROR] 2023-06-15T11:27:11.426Z bd791912-cd83-4274-b5dd-91c7280b9171 An error occurred running the application. Traceback (most recent call last): File "/var/task/mangum/protocols/http.py", line 58, in run await app(self.scope, self.receive, self.send) File "/var/task/fastapi/applications.py", line 276, in __call__ await super().__call__(scope, receive, send) File "/var/task/starlette/applications.py", line 122, in __call__ await self.middleware_stack(scope, receive, send) File "/var/task/starlette/middleware/errors.py", line 184, in __call__ raise exc File "/var/task/starlette/middleware/errors.py", line 162, in __call__ await self.app(scope, receive, _send) File "/var/task/starlette/middleware/cors.py", line 83, in __call__ await self.app(scope, receive, send) File "/var/task/starlette/middleware/exceptions.py", line 79, in __call__ raise exc File "/var/task/starlette/middleware/exceptions.py", line 68, in __call__ await self.app(scope, receive, sender) File "/var/task/fastapi/middleware/asyncexitstack.py", line 21, in __call__ raise e File "/var/task/fastapi/middleware/asyncexitstack.py", line 18, in __call__ await self.app(scope, receive, send) File "/var/task/starlette/routing.py", line 718, in __call__ await route.handle(scope, receive, send) File "/var/task/starlette/routing.py", line 276, in handle await self.app(scope, receive, send) File "/var/task/starlette/routing.py", line 66, in app response = await func(request) File "/var/task/fastapi/routing.py", line 237, in app raw_response = await run_endpoint_function( File "/var/task/fastapi/routing.py", line 165, in run_endpoint_function return await run_in_threadpool(dependant.call, **values) File "/var/task/starlette/concurrency.py", line 41, in run_in_threadpool return await anyio.to_thread.run_sync(func, *args) File "/var/task/anyio/to_thread.py", line 33, in run_sync return await get_asynclib().run_sync_in_worker_thread( File "/var/task/anyio/_backends/_asyncio.py", line 877, in run_sync_in_worker_thread return await future File "/var/task/anyio/_backends/_asyncio.py", line 807, in run result = context.run(func, *args) File "/var/task/routes/users.py", line 28, in login if user and verify_password(form_data.password, user.password): File "/var/task/crud.py", line 15, in verify_password return pwd_context.verify(plain_password, hashed_password) File "/var/task/passlib/context.py", line 2347, in verify return record.verify(secret, hash, **kwds) File "/var/task/passlib/utils/handlers.py", line 792, in verify return consteq(self._calc_checksum(secret), chk) File "/var/task/passlib/handlers/bcrypt.py", line 591, in _calc_checksum self._stub_requires_backend() File "/var/task/passlib/utils/handlers.py", line 2254, in _stub_requires_backend cls.set_backend() File "/var/task/passlib/utils/handlers.py", line 2156, in set_backend return owner.set_backend(name, dryrun=dryrun) File "/var/task/passlib/utils/handlers.py", line 2176, in set_backend raise default_error passlib.exc.MissingBackendError: bcrypt: no backends available -- recommend you install one (e.g. 'pip install bcrypt')
原因分析
- bcrypt是编译型依赖:bcrypt包含C扩展代码,本地安装时会根据你的操作系统(Windows/macOS)编译适配的二进制文件,但AWS Lambda运行在Amazon Linux环境,跨系统编译的二进制文件无法在Lambda中加载,导致依赖失效。
- 打包方式不兼容:直接在本地用
pip install打包部署包,没有针对Lambda环境处理编译型依赖,导致Lambda无法找到可用的bcrypt后端。 - 错误处理掩盖真相:代码中的全局try-except块把
MissingBackendError捕获后统一抛出401错误,初期无法发现真实的依赖问题,移除try-except后才触发500错误,通过CloudWatch日志才定位到根源。
修复方案
方法一:用Lambda层安装兼容的bcrypt
- 构建适配Lambda环境的bcrypt层:
- 用Docker启动Amazon Linux 2容器(或直接用EC2实例),模拟Lambda运行环境
- 创建目录:
mkdir -p python/lib/python3.x/site-packages(x替换为你Lambda使用的Python版本,比如3.9) - 安装bcrypt:
pip install bcrypt -t python/lib/python3.x/site-packages - 打包成zip:
zip -r bcrypt-layer.zip python/ - 上传该zip到Lambda层,然后在你的Lambda函数中关联此层。
方法二:用Docker构建Lambda部署包
- 编写Dockerfile,基于Lambda官方Python镜像构建:
FROM public.ecr.aws/lambda/python:3.9 COPY requirements.txt . RUN pip install -r requirements.txt -t "${LAMBDA_TASK_ROOT}" COPY app.py ${LAMBDA_TASK_ROOT} CMD ["app.handler"] - 构建镜像并推送到ECR,再从ECR部署到Lambda,这样依赖会在Lambda兼容的环境中编译安装。
方法三:替换为纯Python哈希库
如果不想处理编译型依赖的兼容性问题,可以改用纯Python实现的哈希算法,比如passlib的pbkdf2_sha256:
- 修改密码哈希上下文配置:
from passlib.context import CryptContext pwd_context = CryptContext(schemes=["pbkdf2_sha256"], deprecated="auto") - 重新生成现有用户的密码哈希(如果有存量数据),再重新部署代码。
额外优化:改进错误处理
去掉全局try-except,改为捕获特定异常,避免掩盖真实错误:
@route.post("/login", tags=['Account']) def login(form_data: OAuth2PasswordRequestForm = Depends()): try: user = db.query(UserModel).filter_by(email=form_data.username).first() if not user: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect username or password") if not verify_password(form_data.password, user.password): raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect username or password") return {"access_token": signJWT(user), "token_type": "Bearer"} except SQLAlchemyError: db.rollback() raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Database error")
内容的提问来源于stack exchange,提问作者Sardar Jagpreet Singh
相关产品推荐
相关产品推荐

