使用GitHub Actions自动化测试时FastAPI认证失败求助
GitHub Actions中JWT认证测试401失败(本地正常)
8个测试里6个因401未授权失败,仅登录端点@router.post("/token")和验证JWT_SECRET_KEY加载的测试通过,本地测试全部正常。密钥用于签名JWT令牌,认证流程与FastAPI官方OAuth2 JWT实现一致,添加认证前本地和GitHub Actions测试均能通过。
GitHub Actions配置
name: Tests on: push: branches: [ "dev", "main" ] pull_request: branches: [ "main" ] permissions: contents: read jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Python 3.11 uses: actions/setup-python@v3 with: python-version: "3.11" - name: Install dependencies run: | python -m pip install --upgrade pip pip install flake8 pytest pip install -r backend/requirements.txt - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Test backend tests env: JWT_SECRET_KEY: ${{ secrets.JWT_SECRET_KEY }} run: | # run backend tests pytest backend/tests/tests.py - name: Check test status if: ${{ failure() }} run: exit 1
测试代码关键片段
仅展示三个核心测试用例:test_secret_keys(通过)、test_authentication(通过)、test_authenticated_user_access(失败):
import pytest import os from fastapi.testclient import TestClient from .test_utils import FakeUser from settings import ROOT, PROJECT_DIR from api import app from db.database import SessionLocal from db.models import DbBlog automatic_fields = ['timestamp', 'id', 'image_url'] files_dir = os.path.join(ROOT, 'files') client = TestClient(app) user = FakeUser("a", "a") def get_header(): response = client.post("http://localhost:8000/token", data={"grant_type": "password", "username": user.username, "password": user.password}) if response.status_code == 200: access_token = response.json()["access_token"] return {"Authorization": f"Bearer {access_token}"} @pytest.fixture def delete_all_data(): """ This fixture runs on all tests apart from delete. This allows delete to be run on its own as well as with all tests. """ # clean db session = SessionLocal() session.query(DbBlog).delete() session.commit() session.close() # clean dir with images if os.path.exists(files_dir): all_image_files = os.listdir(files_dir) for file in all_image_files: if file != '.gitkeep': os.remove(os.path.join(files_dir, file)) def test_secret_keys(): assert os.environ.get("JWT_SECRET_KEY") is not None def test_authentication(delete_all_data): """ Authenticate fake user and check they can access a restricted endpoint """ response = client.post("http://localhost:8000/token", data={"grant_type": "password", "username": user.username, "password": user.password}) assert response.status_code == 200 def test_authenticated_user_access(delete_all_data): response = client.get("http://localhost:8000/users/me", headers=get_header()) assert response.status_code == 200
认证相关端点代码
@router.post("/token") async def login(form_data: OAuth2PasswordRequestForm = Depends()): user = authenticate_user(fake_users_db, form_data.username, form_data.password) if not user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect username or password", headers={"WWW-Authenticate": "Bearer"}, ) access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) access_token = create_access_token(data={"sub": user.username}, expires_delta=access_token_expires) return {"access_token": access_token, "token_type": "bearer"} @router.get("/users/me") async def read_users_me(current_user: User = Depends(get_current_active_user)): return current_user
可能的解决方案
1. 检查JWT密钥加载时机
应用实例可能在GitHub Actions设置环境变量前就已初始化,导致令牌签名与验证使用的密钥不一致。可以:
- 在测试文件中延迟导入应用,确保环境变量加载后再初始化
TestClient:def test_authenticated_user_access(delete_all_data): from api import app client = TestClient(app) # 重新获取令牌并发起请求 response = client.post("http://localhost:8000/token", data={"grant_type": "password", "username": user.username, "password": user.password}) access_token = response.json()["access_token"] headers = {"Authorization": f"Bearer {access_token}"} response = client.get("http://localhost:8000/users/me", headers=headers) assert response.status_code == 200 - 或在
api.py中改为动态读取环境变量,而非模块加载时一次性读取。
2. 添加调试信息定位问题
在GitHub Actions测试步骤中添加调试输出,确认密钥状态和令牌解码情况:
- name: Test backend tests env: JWT_SECRET_KEY: ${{ secrets.JWT_SECRET_KEY }} run: | echo "JWT_SECRET_KEY loaded: ${{ env.JWT_SECRET_KEY != '' }}" pytest backend/tests/tests.py -s
同时在get_current_active_user函数中添加异常打印,查看令牌验证失败的具体原因。
3. 确认测试用户与令牌有效期
- 检查
fake_users_db在GitHub Actions环境中是否正确初始化了测试用户(登录测试通过说明用户存在,但仍需确认用户状态是否为活跃)。 - 确保
ACCESS_TOKEN_EXPIRE_MINUTES在本地和CI环境中配置一致,避免测试过程中令牌过期。
内容的提问来源于stack exchange,提问作者Ramon Santiago
相关产品推荐
相关产品推荐

