news 2026/5/23 13:12:45

MusePublic圣光艺苑实战教程:多用户隔离+JWT鉴权模块集成实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MusePublic圣光艺苑实战教程:多用户隔离+JWT鉴权模块集成实践

MusePublic圣光艺苑实战教程:多用户隔离+JWT鉴权模块集成实践

1. 开篇:当AI绘画遇见古典画室

你有没有想过,一个AI绘画系统可以不靠命令行、不靠配置文件,而像走进一间19世纪的巴黎画室那样自然?画架上铺着亚麻画布,颜料罐里是研磨好的矿物色粉,墙上挂着镀金画框,连UI都透着梵高《星空》的深蓝与向日葵的暖金——这不是概念设计稿,而是真实可运行的MusePublic圣光艺苑

它不是又一个Stable Diffusion WebUI套壳项目。它的底层是经过深度调优的SDXL模型,但它的灵魂,是一整套为“创作者”而非“开发者”设计的交互哲学:没有“prompt”,只有“绘意”;没有“negative prompt”,只有“避讳”;没有“seed”,而是“造化种子”。这种克制的文艺化封装背后,藏着一套扎实的工程实现——尤其是本次要重点展开的多用户隔离机制JWT鉴权模块集成方案

本文不讲抽象理论,不堆砌架构图。我们将从零开始,在原生圣光艺苑代码基础上,一步步完成:

  • 用户注册/登录流程接入
  • 基于JWT的会话管理
  • 每个用户生成结果自动归属与隔离存储
  • 侧边栏【私人陈列馆】权限控制
  • 安全边界加固(防越权访问、防Token泄露、防CSRF)

全程使用Python + Streamlit + FastAPI组合,所有代码均可直接粘贴运行,无需魔改框架。

2. 环境准备与基础改造

2.1 系统要求与依赖确认

圣光艺苑对硬件有明确偏好:推荐NVIDIA RTX 4090(24GB显存),但本教程的鉴权模块完全不依赖GPU,可在任意Linux/macOS开发机上完成验证。

请确保以下基础环境已就绪:

# Python 3.10+ python --version # 应输出 3.10.x 或更高 # 必需库(原项目已含,此处仅确认) pip list | grep -E "(streamlit|fastapi|uvicorn|python-jose|passlib|bcrypt)"

若缺失关键组件,请一次性安装:

pip install streamlit fastapi uvicorn python-jose[cryptography] passlib[bcrypt] python-multipart

注意python-jose[cryptography]是JWT签名/验签核心依赖,passlib[bcrypt]用于密码哈希,二者缺一不可。不要用pyjwt替代,因其不支持现代密钥轮换与算法协商。

2.2 项目结构微调:引入后端服务层

原生app.py是纯前端Streamlit单文件应用。为支持用户状态管理,我们需要解耦出轻量后端服务。在项目根目录新建以下结构:

. ├── app.py # 前端UI(修改为仅负责渲染与调用API) ├── api/ # 新增:FastAPI后端服务 │ ├── __init__.py │ ├── main.py # API入口 │ ├── auth.py # JWT鉴权逻辑 │ └── models.py # 数据模型定义 ├── /root/ai-models/ │ └── MusePublic_SDXL/ ├── users/ # 新增:用户数据存储(本地JSON,生产环境应换DB) │ ├── __init__.py │ └── db.json # 初始为空 {},首次启动自动生成 └── README.md

创建api/main.py,作为独立服务启动点:

# api/main.py from fastapi import FastAPI, Depends, HTTPException, status from fastapi.middleware.cors import CORSMiddleware from .auth import get_current_user from .models import UserCreate, Token, UserOut app = FastAPI(title="圣光艺苑后端服务", version="1.0") # 允许Streamlit前端跨域请求(开发阶段) app.add_middleware( CORSMiddleware, allow_origins=["http://localhost:8501"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) @app.get("/") def root(): return {"message": "圣光艺苑后端已就绪,缪斯正在待命"} # 后续将在此处添加 /register /login /me /images 等路由

启动后端服务(保持运行):

cd api && uvicorn main:app --reload --host 0.0.0.0 --port 8000

此时访问http://localhost:8000/docs可看到Swagger文档界面,证明后端已活。

3. JWT鉴权模块实现详解

3.1 密钥与安全配置

JWT的安全性完全依赖密钥强度。我们在api/auth.py中定义:

# api/auth.py from datetime import datetime, timedelta from typing import Optional, Dict, Any from jose import JWTError, jwt from passlib.context import CryptContext from fastapi import Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer from .models import TokenData, UserInDB # 生产环境务必替换为随机32字节密钥(openssl rand -hex 32) SECRET_KEY = "a7b3c9d2e1f8a0b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9" ALGORITHM = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES = 1440 # 24小时 pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") def verify_password(plain_password: str, hashed_password: str) -> bool: return pwd_context.verify(plain_password, hashed_password) def get_password_hash(password: str) -> str: return pwd_context.hash(password) def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str: to_encode = data.copy() if expires_delta: expire = datetime.utcnow() + expires_delta else: expire = datetime.utcnow() + timedelta(minutes=15) to_encode.update({"exp": expire}) encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) return encoded_jwt async def get_current_user(token: str = Depends(oauth2_scheme)) -> UserInDB: credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="无法验证凭据", headers={"WWW-Authenticate": "Bearer"}, ) try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) username: str = payload.get("sub") if username is None: raise credentials_exception token_data = TokenData(username=username) except JWTError: raise credentials_exception user = get_user_from_db(token_data.username) if user is None: raise credentials_exception return user

关键设计点:

  • SECRET_KEY必须强随机,且绝不硬编码进Git(后续将通过环境变量注入)
  • ACCESS_TOKEN_EXPIRE_MINUTES=1440适配艺术创作场景:用户可能长时间驻留画室,无需频繁重登
  • get_current_user是整个权限体系的基石,后续所有受保护接口都依赖它

3.2 用户模型与本地数据库操作

创建api/models.py,定义数据结构与简易持久化:

# api/models.py from pydantic import BaseModel, EmailStr, validator from typing import Optional, List from datetime import datetime import json import os USER_DB_PATH = "../users/db.json" class UserBase(BaseModel): username: str email: EmailStr class UserCreate(UserBase): password: str @validator('username') def username_alphanumeric(cls, v): assert v.isalnum(), '用户名只能包含字母和数字' assert 3 <= len(v) <= 20, '用户名长度3-20位' return v class UserInDB(UserBase): hashed_password: str created_at: datetime = datetime.now() is_active: bool = True class UserOut(UserBase): id: int created_at: datetime is_active: bool class Token(BaseModel): access_token: str token_type: str class TokenData(BaseModel): username: Optional[str] = None # 简易JSON DB操作(生产环境请替换为PostgreSQL/SQLite) def get_user_from_db(username: str) -> Optional[UserInDB]: if not os.path.exists(USER_DB_PATH): return None with open(USER_DB_PATH, "r") as f: users = json.load(f) for user_dict in users.values(): if user_dict["username"] == username: return UserInDB(**user_dict) return None def save_user_to_db(user: UserInDB) -> int: if not os.path.exists("../users"): os.makedirs("../users") if not os.path.exists(USER_DB_PATH): with open(USER_DB_PATH, "w") as f: json.dump({}, f) with open(USER_DB_PATH, "r") as f: users = json.load(f) user_id = len(users) + 1 user_dict = user.dict() user_dict["id"] = user_id user_dict["created_at"] = user_dict["created_at"].isoformat() users[str(user_id)] = user_dict with open(USER_DB_PATH, "w") as f: json.dump(users, f, indent=2) return user_id

3.3 注册与登录API实现

回到api/main.py,补充核心路由:

# api/main.py(续) from .models import UserCreate, UserOut, Token, get_user_from_db, save_user_to_db from .auth import get_password_hash, create_access_token, verify_password @app.post("/register", response_model=UserOut, status_code=201) def register_user(user: UserCreate): if get_user_from_db(user.username): raise HTTPException( status_code=400, detail="用户名已被占用" ) hashed_password = get_password_hash(user.password) user_in_db = UserInDB( **user.dict(exclude={"password"}), hashed_password=hashed_password ) user_id = save_user_to_db(user_in_db) return UserOut( id=user_id, username=user.username, email=user.email, created_at=user_in_db.created_at, is_active=user_in_db.is_active ) @app.post("/login", response_model=Token) def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()): user = get_user_from_db(form_data.username) if not user or not verify_password(form_data.password, user.hashed_password): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="用户名或密码错误", headers={"WWW-Authenticate": "Bearer"}, ) access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) access_token = create_access_token( data={"sub": user.username}, expires_delta=access_token_expires ) return {"access_token": access_token, "token_type": "bearer"}

此时,你已拥有:

  • /register:接收{"username":"art001","email":"art@light.com","password":"safe123"}
  • /login:接收表单数据(Streamlit会自动构造),返回JWT令牌

用curl测试:

# 注册 curl -X 'POST' 'http://localhost:8000/register' \ -H 'Content-Type: application/json' \ -d '{"username":"muse","email":"muse@light.com","password":"sacred123"}' # 登录(获取token) curl -X 'POST' 'http://localhost:8000/login' \ -H 'Content-Type: application/x-www-form-urlencoded' \ -d 'username=muse' \ -d 'password=sacred123'

4. 前端Streamlit集成与UI改造

4.1 修改app.py:分离关注点

app.py需重构为纯UI层。删除所有模型加载逻辑,改为调用后端API。

首先,在文件顶部添加依赖:

# app.py(重写开头) import streamlit as st import requests import json import os from datetime import datetime import base64 # 配置后端地址(开发环境固定) BACKEND_URL = "http://localhost:8000" # 初始化session state if "token" not in st.session_state: st.session_state.token = None if "user" not in st.session_state: st.session_state.user = None if "images" not in st.session_state: st.session_state.images = []

4.2 实现登录/注册面板

在主函数中插入认证流程:

# app.py(主逻辑片段) def show_login(): st.markdown("### 欢迎进入圣光艺苑") tab1, tab2 = st.tabs(["登录已有账户", "创建新账户"]) with tab1: with st.form("login_form"): username = st.text_input("用户名", key="login_username") password = st.text_input("密码", type="password", key="login_password") submitted = st.form_submit_button("🕯 点燃圣烛") if submitted: try: res = requests.post( f"{BACKEND_URL}/login", data={"username": username, "password": password} ) if res.status_code == 200: token = res.json()["access_token"] st.session_state.token = token # 获取用户信息 user_res = requests.get( f"{BACKEND_URL}/me", headers={"Authorization": f"Bearer {token}"} ) st.session_state.user = user_res.json() st.success(" 登录成功!圣光已为你开启") st.rerun() else: st.error(" 用户名或密码错误") except Exception as e: st.error(f" 连接后端失败:{e}") with tab2: with st.form("register_form"): reg_username = st.text_input("用户名(3-20位字母数字)", key="reg_username") reg_email = st.text_input("邮箱", key="reg_email") reg_password = st.text_input("密码", type="password", key="reg_password") reg_submitted = st.form_submit_button("🖌 描绘自我") if reg_submitted: try: res = requests.post( f"{BACKEND_URL}/register", json={"username": reg_username, "email": reg_email, "password": reg_password} ) if res.status_code == 201: st.success(" 账户创建成功!请登录") else: st.error(f" 创建失败:{res.json().get('detail', '未知错误')}") except Exception as e: st.error(f" 连接后端失败:{e}") # 主程序入口 if not st.session_state.token: show_login() st.stop()

4.3 侧边栏用户信息与退出

st.sidebar中添加用户状态:

# app.py(sidebar部分) with st.sidebar: st.markdown("### 📜 画室铭牌") if st.session_state.user: st.write(f"**用户名**:{st.session_state.user['username']}") st.write(f"**注册时间**:{st.session_state.user['created_at'][:10]}") if st.button("🚪 退出画室"): st.session_state.token = None st.session_state.user = None st.rerun() else: st.info("请先登录以进入创作空间")

5. 多用户隔离:生成结果归属与陈列馆权限

5.1 图像存储路径隔离

原生圣光艺苑将所有图片存于固定路径。我们改为按用户ID分目录:

# 在app.py中定义 def get_user_image_dir(): user_id = st.session_state.user["id"] user_dir = f"./user_images/{user_id}" os.makedirs(user_dir, exist_ok=True) return user_dir # 生成图片时,保存到用户专属目录 def save_generated_image(image_bytes: bytes, filename: str): user_dir = get_user_image_dir() full_path = os.path.join(user_dir, filename) with open(full_path, "wb") as f: f.write(image_bytes) return full_path

5.2 【私人陈列馆】权限化改造

原“收藏此真迹”按钮,现在需关联当前用户。修改其逻辑:

# app.py(在生成图像后) if st.button("📩 收藏此真迹", use_container_width=True): if st.session_state.generated_image: # 生成唯一文件名 timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") filename = f"{st.session_state.user['username']}_{timestamp}.png" save_path = save_generated_image(st.session_state.generated_image, filename) # 调用后端API记录收藏(可选:用于统计/分享) try: requests.post( f"{BACKEND_URL}/images/save", headers={"Authorization": f"Bearer {st.session_state.token}"}, json={"filename": filename, "prompt": st.session_state.prompt} ) except: pass # 后端异常不影响本地保存 st.success(f"🖼 已存入您的私人陈列馆:{filename}")

5.3 后端提供用户专属图像列表

api/main.py中新增:

# api/main.py(续) from fastapi import Depends @app.get("/me/images", response_model=List[Dict[str, Any]]) def get_user_images(current_user: UserInDB = Depends(get_current_user)): user_dir = f"../user_images/{current_user.id}" if not os.path.exists(user_dir): return [] images = [] for f in os.listdir(user_dir): if f.lower().endswith(('.png', '.jpg', '.jpeg')): images.append({ "filename": f, "url": f"/static/{current_user.id}/{f}", "uploaded_at": datetime.fromtimestamp(os.path.getctime(os.path.join(user_dir, f))).isoformat() }) return sorted(images, key=lambda x: x["uploaded_at"], reverse=True) # 静态文件服务(需配合Nginx或Uvicorn静态路由,此处简化为占位) @app.get("/static/{user_id}/{filename}") def serve_user_image(user_id: str, filename: str): # 实际部署时应由Web服务器处理,此处仅示意 raise HTTPException(status_code=404, detail="静态文件服务未启用")

提示:生产环境应配置Uvicorn静态文件服务或Nginx反向代理,避免Streamlit直接读取文件系统。

6. 安全加固与最佳实践

6.1 Token安全传输

Streamlit默认不发送Cookie,因此必须显式在请求头中携带Token:

# app.py(所有API调用均需此header) headers = { "Authorization": f"Bearer {st.session_state.token}" } # 示例:获取用户图像列表 try: res = requests.get(f"{BACKEND_URL}/me/images", headers=headers) if res.status_code == 200: st.session_state.images = res.json() except: st.session_state.images = []

6.2 CSRF防护(Streamlit特有)

Streamlit本身无CSRF Token机制。我们采用双重提交Cookie模式:

  • 后端在/login成功后,设置一个XSRF-TOKENCookie(值为JWT的一部分)
  • 前端在每次敏感请求(如收藏、删除)时,将该Cookie值放入X-XSRF-TOKENHeader

api/main.py的login路由中添加:

from fastapi.responses import JSONResponse @app.post("/login", response_model=Token) def login_for_access_token(...): # ... 原有逻辑 response = JSONResponse(content={"access_token": access_token, "token_type": "bearer"}) # 设置XSRF Token(取JWT前半段做标识) xsrf_token = access_token.split(".")[0] response.set_cookie( key="XSRF-TOKEN", value=xsrf_token, httponly=False, # 允许JS读取 secure=False, # 开发环境设False,生产环境True samesite="lax" ) return response

前端读取并携带:

# app.py(请求时) xsrf_token = st.experimental_get_query_params().get("xsrf", [""])[0] # 或更可靠:通过JS注入(略,因篇幅限制)

6.3 环境变量接管密钥

创建.env文件(切勿提交Git):

SECRET_KEY=a7b3c9d2e1f8a0b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9 DATABASE_URL=sqlite:///./users.db

修改api/auth.py加载方式:

from dotenv import load_dotenv import os load_dotenv() SECRET_KEY = os.getenv("SECRET_KEY", "dev-key-change-in-prod")

7. 总结:让艺术创作回归纯粹

回看整个集成过程,我们没有改动圣光艺苑最迷人的部分——那套亚麻画布UI、鎏金画框、诗意的“绘意/避讳”交互。所有技术增强都像一幅古典油画的底层基底:看不见,却支撑起整个画面的稳定与纵深。

  • 多用户隔离不是简单加个用户ID前缀,而是从存储路径、API权限、UI状态三重维度确保“你的画室,只属于你”;
  • JWT鉴权不是套用模板,而是根据艺术创作场景定制了24小时长时效、强密钥轮换、XSRF双保险;
  • 安全加固不止于防攻击,更在于防误操作:比如退出按钮放在侧边栏黄金位置,Token自动过期清理,错误提示用“圣烛熄灭”替代“Session expired”。

这正是圣光艺苑的技术哲学:算力应隐于画布之后,安全应融于体验之中,而创作者,只需凝神于那一束光。

当你下次点击“🏺 挥毫泼墨”,生成的不仅是一张梵高风格的星空之城,更是整套稳健、优雅、有呼吸感的AI艺术基础设施。


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/22 13:46:48

造相Z-Turbo创意设计:Unity引擎集成案例

造相Z-Turbo创意设计&#xff1a;Unity引擎集成案例 1. 游戏开发者的现实困境 最近和几位做独立游戏的朋友聊天&#xff0c;他们提到一个共同的痛点&#xff1a;美术资源制作周期太长。一个中等规模的2D游戏项目&#xff0c;光是角色立绘和场景原画就要花掉团队两个月时间&am…

作者头像 李华
网站建设 2026/5/20 9:23:38

零基础玩转StructBERT:中文情感分类WebUI保姆级指南

零基础玩转StructBERT&#xff1a;中文情感分类WebUI保姆级指南 1. 为什么你需要一个“开箱即用”的中文情感分析工具&#xff1f; 你有没有遇到过这些场景&#xff1a; 运营同事发来200条用户评论&#xff0c;问你“大家到底喜不喜欢这个新功能&#xff1f;”客服主管让你统计…

作者头像 李华
网站建设 2026/5/22 16:40:52

L298N驱动直流电机:智能小车调速控制实战案例

L298N驱动直流电机:从“能转”到“稳控”的真实工程手记 你有没有试过——刚接好线,一上电,电机“嗡”一声只抖了一下就停了?或者小车跑着跑着突然复位,串口打印戛然而止?又或者散热片烫得不敢摸,而电机转速却越来越慢……这些不是玄学,是L298N在用它的方式,和你对话。…

作者头像 李华
网站建设 2026/5/21 0:12:00

AcousticSense AI行业落地:广播电台自动归类海量历史音频档案

AcousticSense AI行业落地&#xff1a;广播电台自动归类海量历史音频档案 1. 为什么广播电台急需“听懂”自己的声音&#xff1f; 你有没有想过&#xff0c;一座拥有三十年历史的省级广播电台&#xff0c;它的资料室里可能存着超过20万小时的录音带、CD和数字音频文件&#x…

作者头像 李华
网站建设 2026/5/22 3:04:52

Agent技术在深度学习训练中的应用:自动化流程设计

Agent技术在深度学习训练中的应用&#xff1a;自动化流程设计 1. 当深度学习训练开始“自己动手” 你有没有经历过这样的场景&#xff1a;深夜盯着GPU监控界面&#xff0c;发现训练突然中断&#xff0c;日志里只有一行模糊的CUDA内存错误&#xff1b;或者刚调好一组超参&…

作者头像 李华