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

FastAPI中如何让Swagger Docs登录后保持授权状态?

解决方案:FastAPI OAuth2 认证保护 Swagger Docs 并自动延续认证状态

核心实现思路

通过 Cookie 存储 Bearer Token,自定义 Swagger UI 页面完成登录校验与自动认证注入,复用 OAuth2 原生逻辑确保接口与文档的认证规则一致,最终实现:

  • 未登录用户访问 Docs 自动跳转登录页
  • 登录后 Docs 自动读取 Cookie 中的 Token,测试接口无需手动重复认证
  • Token 过期或无效时自动清理 Cookie 并跳转登录

完整代码实现

1. 依赖导入与基础配置

from fastapi import FastAPI, Request, Response, Depends, HTTPException, status, Form
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.openapi.docs import get_swagger_ui_html
from jose import JWTError, jwt
from typing import Optional

# OAuth2 与 JWT 核心配置(替换为你的实际参数)
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token")
SECRET_KEY = "your-production-secret-key"
ALGORITHM = "HS256"

# 初始化应用,关闭默认文档路由
app = FastAPI(docs_url=None, redoc_url=None)
templates = Jinja2Templates(directory="templates")

2. 登录页面与登录接口

@app.get("/", include_in_schema=False, response_class=HTMLResponse)
async def login_page(request: Request):
    error = request.query_params.get("error")
    expired = request.query_params.get("expired")
    return templates.TemplateResponse(
        "login.html", 
        {"request": request, "error": error, "expired": expired}
    )

@app.post("/login", include_in_schema=False)
async def perform_login(
    request: Request,
    response: Response,
    username: str = Form(...),
    password: str = Form(...),
):
    try:
        # 复用 OAuth2 原生的 token 生成逻辑
        form_data = OAuth2PasswordRequestForm(username=username, password=password, scope="")
        token_data = await generate_token(form_data)
        
        # 设置认证 Cookie(HttpOnly 防止 XSS,生产环境建议开启 secure=True)
        response = RedirectResponse("/docs", status_code=status.HTTP_302_FOUND)
        response.set_cookie(
            key="Authorization",
            value=f"{token_data['token_type'].capitalize()} {token_data['access_token']}",
            httponly=True,
            secure=False,
            samesite="lax"
        )
        return response
    except HTTPException:
        return RedirectResponse("/?error=true", status_code=status.HTTP_303_SEE_OTHER)

3. 自定义 Swagger UI(核心:自动注入认证 Token)

@app.get("/docs", include_in_schema=False)
async def custom_swagger_ui_html(request: Request):
    auth_cookie = request.cookies.get("Authorization")
    # 未登录直接跳转登录页
    if not auth_cookie:
        return RedirectResponse("/", status_code=status.HTTP_303_SEE_OTHER)
    
    # 验证 Cookie 中的 Token 有效性
    try:
        _, access_token = auth_cookie.split(" ")
        decode_token(access_token)
    except (ValueError, JWTError):
        response = RedirectResponse("/?expired=true", status_code=status.HTTP_303_SEE_OTHER)
        response.delete_cookie("Authorization")
        return response
    
    # 注入 JS 自动读取 Cookie 并设置 Swagger 全局认证头
    return get_swagger_ui_html(
        openapi_url="/openapi.json",
        title="API Documentation",
        swagger_ui_init_oauth={"clientId": "fastapi-swagger"},
        extra_head='''
        <script>
        window.onload = function() {
            const authCookie = document.cookie.split('; ').find(row => row.startsWith('Authorization='));
            if (authCookie) {
                const token = authCookie.split('=')[1].split(' ')[1];
                const ui = SwaggerUIBundle({
                    url: "/openapi.json",
                    dom_id: '#swagger-ui',
                    presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset],
                    layout: "BaseLayout"
                });
                // 自动设置 Bearer Token 到所有接口
                ui.preauthorizeApiKey('Bearer', token);
            }
        };
        </script>
        '''
    )

4. 认证核心逻辑(复用 OAuth2 流程)

def authenticate(username: str, password: str) -> Optional[dict]:
    # 替换为你的实际用户校验逻辑
    if username == "test" and password == "test":
        return {"username": username, "role": "user"}
    return None

def create_token(user: dict) -> str:
    # 替换为你的实际 JWT 生成逻辑(可添加过期时间)
    encoded_jwt = jwt.encode(user, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt

def decode_token(token: str) -> dict:
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        return payload
    except JWTError:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid or expired token"
        )

@app.post("/token", include_in_schema=False)
async def generate_token(form_data: OAuth2PasswordRequestForm = Depends(), cached_token=None):
    if cached_token:
        try:
            _, access_token = cached_token["Authorization"].split(" ")
            decode_token(access_token)
            return {"access_token": access_token, "token_type": "bearer"}
        except:
            raise HTTPException(status_code=401, detail="Invalid token in cookie")
    
    user = authenticate(form_data.username, form_data.password)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid username/password"
        )
    token = create_token(user)
    return {"access_token": token, "token_type": "bearer"}

5. 受保护接口示例

@app.get("/protected/resource")
async def protected_resource(token: str = Depends(oauth2_scheme)):
    payload = decode_token(token)
    return {"message": f"Hello {payload['username']}, this is a protected resource"}

6. 登录页模板(templates/login.html)

<!DOCTYPE html>
<html>
<head>
    <title>API Docs Login</title>
</head>
<body>
    <h1>Login to Access API Documentation</h1>
    {% if error %}
        <p style="color: red;">Invalid username or password!</p>
    {% endif %}
    {% if expired %}
        <p style="color: red;">Session expired, please login again!</p>
    {% endif %}
    <form method="post" action="/login">
        <div>
            <label>Username:</label>
            <input type="text" name="username" required>
        </div>
        <div>
            <label>Password:</label>
            <input type="password" name="password" required>
        </div>
        <button type="submit">Login</button>
    </form>
</body>
</html>

关键改进说明

  1. Swagger 自动认证:通过注入自定义 JS,在页面加载时读取 Cookie 中的 Token,调用 Swagger UI 的 preauthorizeApiKey 方法全局设置认证头,测试接口时无需手动输入 Token
  2. 安全强化:Cookie 设置 HttpOnly 防止 XSS 攻击,生产环境建议开启 secure=True(仅 HTTPS 传输)
  3. 逻辑复用:所有认证流程完全基于 OAuth2PasswordBearer 和 JWT,确保文档与接口的认证规则一致
  4. 状态校验:访问 Docs 时自动验证 Token 有效性,过期或无效时清理 Cookie 并跳转登录页

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 19:05:22