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>
关键改进说明
- Swagger 自动认证:通过注入自定义 JS,在页面加载时读取 Cookie 中的 Token,调用 Swagger UI 的
preauthorizeApiKey方法全局设置认证头,测试接口时无需手动输入 Token - 安全强化:Cookie 设置
HttpOnly防止 XSS 攻击,生产环境建议开启secure=True(仅 HTTPS 传输) - 逻辑复用:所有认证流程完全基于 OAuth2PasswordBearer 和 JWT,确保文档与接口的认证规则一致
- 状态校验:访问 Docs 时自动验证 Token 有效性,过期或无效时清理 Cookie 并跳转登录页
内容的提问来源于stack exchange,提问作者yeeshue99
相关产品推荐
相关产品推荐

