FastAPI集成Jinja2与Keycloak:实现页面访问自动重定向认证求助
解决FastAPI多页网站自动重定向Keycloak登录的方案
核心问题是你当前的依赖在未认证时返回401 JSON错误,而页面访问需要触发重定向到Keycloak登录页。以下是具体实现方案:
1. 配置基础依赖与Keycloak参数
先安装必要依赖:pip install fastapi uvicorn requests python-jose python-multipart
然后定义Keycloak配置和会话中间件(用于跨请求存储token):
from fastapi import FastAPI, Depends, Request, HTTPException from fastapi.responses import RedirectResponse, TemplateResponse from fastapi.middleware.session import SessionMiddleware from jose import JWTError, jwt from pydantic import BaseModel import requests # 替换为你的Keycloak实际配置 KEYCLOAK_REALM = "your-realm-name" KEYCLOAK_CLIENT_ID = "your-client-id" KEYCLOAK_CLIENT_SECRET = "your-client-secret" KEYCLOAK_DOMAIN = "https://your-keycloak-domain.com" KEYCLOAK_AUTH_ENDPOINT = f"{KEYCLOAK_DOMAIN}/auth/realms/{KEYCLOAK_REALM}/protocol/openid-connect/auth" KEYCLOAK_TOKEN_ENDPOINT = f"{KEYCLOAK_DOMAIN}/auth/realms/{KEYCLOAK_REALM}/protocol/openid-connect/token" KEYCLOAK_CALLBACK_URL = "http://localhost:8000/callback" app = FastAPI() # 会话中间件的secret_key请用随机安全字符串,生产环境不要硬编码 app.add_middleware(SessionMiddleware, secret_key="your-strong-session-secret") # 用户信息模型 class User(BaseModel): username: str realm_roles: list[str]
2. 修改认证依赖实现自动重定向
重写get_user_info依赖,当检测到未认证或token无效时,直接触发重定向到Keycloak登录页:
def get_user_info(request: Request): access_token = request.session.get("access_token") # 无token则触发登录重定向 if not access_token: auth_url = ( f"{KEYCLOAK_AUTH_ENDPOINT}?response_type=code" f"&client_id={KEYCLOAK_CLIENT_ID}" f"&redirect_uri={KEYCLOAK_CALLBACK_URL}" f"&scope=openid+profile+roles" # 存储当前访问的页面,认证后返回原页面 f"&state={request.url.path}" ) raise HTTPException( status_code=307, headers={"Location": auth_url} ) # 验证并解析token try: # 如果Keycloak用RS256算法,需要从JWKS端点获取公钥,此处简化为HS256,根据实际配置调整 payload = jwt.decode( access_token, KEYCLOAK_CLIENT_SECRET, algorithms=["HS256"], audience=KEYCLOAK_CLIENT_ID ) return User( username=payload.get("preferred_username"), realm_roles=payload.get("realm_access", {}).get("roles", []) ) except JWTError: # token无效,清除会话并重定向登录 request.session.pop("access_token", None) raise HTTPException( status_code=307, headers={"Location": KEYCLOAK_AUTH_ENDPOINT} )
3. 实现回调接口处理授权码交换
添加回调接口接收Keycloak返回的授权码,交换为access token并存入会话:
@app.get("/callback") async def keycloak_callback(request: Request, code: str, state: str = "/"): # 用授权码交换access token token_payload = { "grant_type": "authorization_code", "code": code, "redirect_uri": KEYCLOAK_CALLBACK_URL, "client_id": KEYCLOAK_CLIENT_ID, "client_secret": KEYCLOAK_CLIENT_SECRET } token_response = requests.post(KEYCLOAK_TOKEN_ENDPOINT, data=token_payload) if token_response.status_code != 200: return TemplateResponse("login_error.html", {"request": request, "error": token_response.text}) token_data = token_response.json() request.session["access_token"] = token_data.get("access_token") # 重定向回用户最初访问的页面(通过state参数传递) return RedirectResponse(url=state)
4. 保护你的页面接口
现在用修改后的依赖保护页面接口,访问时会自动重定向到Keycloak登录:
@app.get("/secure") async def secure_page(request: Request, user: User = Depends(get_user_info)): return TemplateResponse( "secure.html", { "request": request, "username": user.username, "user_roles": user.realm_roles } )
关键注意事项
- Keycloak客户端配置:确保客户端的「Valid Redirect URIs」包含你的回调URL(如
http://localhost:8000/callback),否则Keycloak会拒绝回调请求。 - Token算法:如果你的Keycloak使用RS256非对称加密,需要从Keycloak的JWKS端点获取公钥来验证token,不要直接用客户端密钥。
- 会话安全:生产环境中
SessionMiddleware的secret_key要使用随机生成的安全字符串,避免硬编码;同时可以配置会话过期时间。
内容的提问来源于stack exchange,提问作者Tema Torg
相关产品推荐
相关产品推荐

