如何结合FastAPI安全认证与Bootstrap5实现登录跳转功能
一、FastAPI 后端配置(JWT存入Cookie)
1. 安装核心依赖
pip install fastapi uvicorn python-jose[cryptography] passlib[bcrypt] python-multipart
2. 完整后端代码实现
from fastapi import FastAPI, Depends, HTTPException, status from fastapi.security import OAuth2PasswordRequestForm from fastapi.responses import JSONResponse from jose import JWTError, jwt from passlib.context import CryptContext from datetime import datetime, timedelta from pydantic import BaseModel # 核心配置(生产环境用环境变量加载) SECRET_KEY = "your-random-secret-key-here" ALGORITHM = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES = 30 pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") app = FastAPI() # 模拟用户数据库 fake_users_db = { "johndoe": { "username": "johndoe", "hashed_password": pwd_context.hash("secret123"), "email": "johndoe@example.com", } } class User(BaseModel): username: str email: str | None = None class UserInDB(User): hashed_password: str # 密码验证与用户认证 def verify_password(plain_password, hashed_password): return pwd_context.verify(plain_password, hashed_password) def get_user(db, username: str): if username in db: return UserInDB(**db[username]) def authenticate_user(fake_db, username: str, password: str): user = get_user(fake_db, username) if not user or not verify_password(password, user.hashed_password): return False return user # JWT令牌生成 def create_access_token(data: dict, expires_delta: timedelta | None = None): to_encode = data.copy() expire = datetime.utcnow() + (expires_delta or timedelta(minutes=15)) to_encode.update({"exp": expire}) return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) # 登录接口:将JWT存入HttpOnly Cookie @app.post("/login") async def login_for_access_token(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 = create_access_token( data={"sub": user.username}, expires_delta=timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) ) # 设置安全Cookie response = JSONResponse(content={"msg": "Login successful"}) response.set_cookie( key="access_token", value=f"Bearer {access_token}", httponly=True, max_age=ACCESS_TOKEN_EXPIRE_MINUTES*60, samesite="Lax", # secure=True, # 生产环境HTTPS下启用 ) return response # 认证依赖:从Cookie读取并验证JWT async def get_current_user(): credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials", headers={"WWW-Authenticate": "Bearer"}, ) try: # 从Cookie获取令牌 from fastapi import Request request: Request = Depends() token = request.cookies.get("access_token") if not token: raise credentials_exception payload = jwt.decode(token.split(" ")[1], SECRET_KEY, algorithms=[ALGORITHM]) username: str = payload.get("sub") if not username or not get_user(fake_users_db, username): raise credentials_exception return get_user(fake_users_db, username) except JWTError: raise credentials_exception # 需要认证的About接口 @app.get("/api/about") async def read_about(current_user: User = Depends(get_current_user)): return {"message": f"Welcome to About page, {current_user.username}!"}
二、Bootstrap5 前端实现(登录跳转+认证检查)
1. 登录页面(login.html)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Login</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <div class="container mt-5"> <div class="row justify-content-center"> <div class="col-md-6"> <div class="card"> <div class="card-header"> <h3 class="text-center">Login</h3> </div> <div class="card-body"> <form id="loginForm"> <div class="mb-3"> <label for="username" class="form-label">Username</label> <input type="text" class="form-control" id="username" required> </div> <div class="mb-3"> <label for="password" class="form-label">Password</label> <input type="password" class="form-control" id="password" required> </div> <button type="submit" class="btn btn-primary w-100">Login</button> </form> <div id="errorMsg" class="mt-3 text-danger text-center d-none"></div> </div> </div> </div> </div> </div> <script> document.getElementById('loginForm').addEventListener('submit', async (e) => { e.preventDefault(); const username = document.getElementById('username').value; const password = document.getElementById('password').value; const formData = new FormData(); formData.append('username', username); formData.append('password', password); try { const response = await fetch('/login', { method: 'POST', body: formData }); if (response.ok) { // 登录成功自动跳转About页面 window.location.href = '/about.html'; } else { const res = await response.json(); document.getElementById('errorMsg').textContent = res.detail; document.getElementById('errorMsg').classList.remove('d-none'); } } catch (err) { document.getElementById('errorMsg').textContent = 'Login failed, please try again'; document.getElementById('errorMsg').classList.remove('d-none'); } }); </script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> </body> </html>
2. About页面(about.html)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>About</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <div class="container mt-5"> <h1 class="text-center">About Page</h1> <div id="welcomeMsg" class="mt-4 text-center fs-5"></div> <button id="logoutBtn" class="btn btn-danger mt-4 d-block mx-auto">Logout</button> </div> <script> // 页面加载时检查认证状态 async function checkAuth() { try { const response = await fetch('/api/about', { method: 'GET', credentials: 'include' // 自动携带Cookie }); if (response.ok) { const data = await response.json(); document.getElementById('welcomeMsg').textContent = data.message; } else { // 认证失败跳回登录页 window.location.href = '/login.html'; } } catch (err) { window.location.href = '/login.html'; } } // 登出:清除Cookie并跳转登录页 document.getElementById('logoutBtn').addEventListener('click', () => { document.cookie = "access_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; window.location.href = '/login.html'; }); window.onload = checkAuth; </script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> </body> </html>
三、关键注意事项
- Cookie安全:生产环境必须启用
secure=True,确保Cookie仅在HTTPS传输;httponly=True防止前端JS读取Cookie,规避XSS风险;samesite="Lax"降低CSRF攻击概率。 - 密钥管理:不要硬编码
SECRET_KEY,用环境变量加载,可通过openssl rand -hex 32生成随机安全密钥。 - 前端请求:所有需要认证的接口请求必须添加
credentials: 'include',确保浏览器自动携带Cookie。 - 过期处理:Cookie的
max_age与JWT过期时间保持一致,过期后前端会自动跳回登录页。
内容的提问来源于stack exchange,提问作者brad
相关产品推荐
相关产品推荐

