如何基于Gradio构建带令牌功能的完整登录系统?
基于Gradio实现完整身份验证系统方案
一、仅用Gradio+Python实现的思路
Gradio自带的auth参数仅支持简单用户名密码校验,要实现带access/refresh token、过期续期及登出的完整系统,需结合Gradio的Request对象和会话管理,用Python内存存储或轻量持久化方案维护token状态:
1. 核心逻辑
- Token生成与存储:用户名密码验证通过后,生成短期有效access token和长期有效refresh token,关联用户信息存储,同时记录过期时间。
- 请求校验:聊天接口前置token校验,通过
gr.Request获取请求头中的access token,判断有效性;若过期,用refresh token生成新的access token。 - 登出处理:删除用户对应的token记录,使后续请求失效。
2. 完整代码实现
import gradio as gr import random import time import jwt from datetime import datetime, timedelta from typing import Optional # 配置参数 USERNAME = "admin" PASSWORD = "123456" SECRET_KEY = "your-secure-secret-key" # 生产环境务必使用随机强密钥 ACCESS_TOKEN_EXPIRE_MINUTES = 30 REFRESH_TOKEN_EXPIRE_DAYS = 7 # 内存存储token(生产环境建议替换为Redis等持久化存储) active_tokens = {} def create_token(data: dict, expires_delta: Optional[timedelta] = 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="HS256") def authenticate(username, password): if username == USERNAME and password == PASSWORD: # 生成access和refresh token access_token = create_token( data={"sub": username}, expires_delta=timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) ) refresh_token = create_token( data={"sub": username, "type": "refresh"}, expires_delta=timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS) ) # 存储refresh token用于续期 active_tokens[username] = { "refresh_token": refresh_token, "expires_at": datetime.utcnow() + timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS) } return True, access_token, refresh_token return False, "", "" def refresh_access_token(refresh_token): try: payload = jwt.decode(refresh_token, SECRET_KEY, algorithms=["HS256"]) username = payload.get("sub") if payload.get("type") != "refresh" or username not in active_tokens: return "", "无效的refresh token" if datetime.utcnow() > datetime.fromtimestamp(payload["exp"]): del active_tokens[username] return "", "refresh token已过期,请重新登录" # 生成新access token new_access_token = create_token( data={"sub": username}, expires_delta=timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) ) return new_access_token, "" except jwt.ExpiredSignatureError: return "", "refresh token已过期,请重新登录" except jwt.InvalidTokenError: return "", "无效的refresh token" def check_access_token(request: gr.Request): auth_header = request.headers.get("Authorization") if not auth_header or not auth_header.startswith("Bearer "): return False, "请提供有效的access token" access_token = auth_header.split(" ")[1] try: payload = jwt.decode(access_token, SECRET_KEY, algorithms=["HS256"]) username = payload.get("sub") if username not in active_tokens: return False, "用户未登录" return True, "" except jwt.ExpiredSignatureError: return False, "access token已过期,请刷新token" except jwt.InvalidTokenError: return False, "无效的access token" def logout(username): if username in active_tokens: del active_tokens[username] return "已成功登出" def respond(message, chat_history, request: gr.Request): # 前置token校验 valid, err_msg = check_access_token(request) if not valid: return "", chat_history + [(message, f"错误:{err_msg}")] bot_message = random.choice(["你好呀!", "我有点饿了😋"]) chat_history.append((message, bot_message)) time.sleep(2) return "", chat_history with gr.Blocks() as demo: # 登录标签页 with gr.Tab("登录"): username_input = gr.Textbox(label="用户名") password_input = gr.Textbox(label="密码", type="password") login_btn = gr.Button("登录") login_status = gr.Textbox(label="登录状态", interactive=False) access_token_display = gr.Textbox(label="Access Token", interactive=False) refresh_token_display = gr.Textbox(label="Refresh Token", interactive=False) login_btn.click( authenticate, inputs=[username_input, password_input], outputs=[gr.State(), access_token_display, refresh_token_display] ).then( lambda success, token: f"登录成功!Access Token:{token}" if success else "用户名或密码错误", inputs=[gr.State(), access_token_display], outputs=login_status ) # Token刷新标签页 with gr.Tab("刷新Token"): refresh_token_input = gr.Textbox(label="Refresh Token") refresh_btn = gr.Button("刷新Access Token") new_access_token_output = gr.Textbox(label="新Access Token", interactive=False) refresh_status = gr.Textbox(label="刷新状态", interactive=False) refresh_btn.click( refresh_access_token, inputs=[refresh_token_input], outputs=[new_access_token_output, refresh_status] ) # 聊天标签页 with gr.Tab("聊天"): chatbot = gr.Chatbot() msg = gr.Textbox(label="输入消息") clear = gr.ClearButton([msg, chatbot]) logout_btn = gr.Button("登出") logout_status = gr.Textbox(label="登出状态", interactive=False) msg.submit( respond, inputs=[msg, chatbot], outputs=[msg, chatbot], api_name="chat" ) logout_btn.click( logout, inputs=[username_input], outputs=logout_status ) if __name__ == "__main__": demo.launch(share=True)
3. 关键注意点
- 内存存储
active_tokens仅适合测试场景,生产环境必须用Redis等分布式存储维护token,避免服务重启后数据丢失。 SECRET_KEY要使用随机生成的强字符串,绝对不能泄露。- 前端调用聊天接口时,需将access token放在请求头
Authorization: Bearer <token>中,Gradio的Request对象可直接读取该头信息。
二、兼容Gradio的第三方框架推荐
如果需要更成熟的生产级身份验证机制,可结合以下Python框架与Gradio集成:
- FastAPI:支持标准OAuth2.0流程,内置JWT工具,可快速实现access/refresh token、token黑名单、登出等功能。将Gradio应用挂载为FastAPI的子应用,由FastAPI处理身份验证拦截,Gradio专注UI交互。
- Flask-Login:基于Flask的登录扩展,提供会话管理、token续期等功能。将Gradio嵌入Flask应用,通过Flask路由拦截实现身份校验,适合已有Flask项目的场景。
- Django REST Framework:若项目基于Django,可利用DRF的TokenAuthentication或JWTAuthentication组件,结合Django的用户系统和权限控制,实现完整身份验证流程,再集成Gradio界面。
这些框架提供了更完善的安全机制和生态支持,适合生产环境部署,与Gradio结合可兼顾功能完整性和交互体验。
内容的提问来源于stack exchange,提问作者Robin Kuri
相关产品推荐
相关产品推荐

