手机检测WebUI权限管理:基于OAuth2的多角色(管理员/监考员)控制
手机检测WebUI权限管理:基于OAuth2的多角色(管理员/监考员)控制
1. 项目背景与需求
想象一下,你部署了一个手机检测系统,用来在考场里监控学生有没有偷偷用手机。系统跑起来了,效果也不错,但很快你发现一个问题:所有人都能访问这个系统,谁都能上传图片、查看检测结果。这显然不行。
监考老师需要用它来实时监控,但你不能让他们有权限去修改系统设置或者删除日志。系统管理员需要维护整个服务,但又不能让他们在考试期间随意操作影响监考。这就是权限管理的核心需求——不同的人,干不同的事,看到不同的界面。
今天要聊的,就是怎么给这个基于DAMO-YOLO和TinyNAS的手机检测WebUI,加上一套靠谱的权限控制系统。我们选择OAuth2这个行业标准协议来实现,它能清晰地划分出“管理员”和“监考员”两种角色,让系统既安全又好用。
2. 为什么选择OAuth2?
你可能听过OAuth2,它常被用在“用微信登录”、“用GitHub账号登录”这种场景。其实,它同样非常适合我们这种内部系统的权限管理。
简单来说,OAuth2解决了几个关键问题:
- 不用自己管密码:系统不需要存储用户的明文密码,安全性更高。
- 权限分得清:可以精确控制某个角色能访问哪些接口(API),能进行哪些操作。
- 令牌化访问:用户登录后拿到一个“令牌”(Token),后续操作都靠这个令牌来识别身份和权限,过期了可以刷新。
- 标准协议:方案成熟,有大量现成的库和工具支持,自己从头写的坑太多了。
对于我们的手机检测系统,OAuth2能让我们轻松实现:
- 监考员:只能访问检测页面,上传图片,查看本次检测结果。
- 管理员:可以访问所有页面,包括用户管理、日志查看、服务配置等。
3. 系统架构与核心概念
在动手之前,我们先理清两个核心概念和整个系统的架构。
3.1 角色定义(Role Definition)
我们定义两种角色,权限由高到低:
管理员 (Admin):
- 权限:拥有系统的全部操作权限。
- 可访问功能:手机检测、用户管理(增删改查监考员)、查看所有操作日志、系统服务管理(重启、停止)、模型配置管理。
- 典型用户:IT运维人员、系统负责人。
监考员 (Proctor):
- 权限:仅拥有执行检测任务的相关权限。
- 可访问功能:手机检测页面(上传图片、查看检测结果)、查看本人的操作历史。
- 典型用户:监考老师、考场工作人员。
3.2 权限范围(Scope)
Scope是OAuth2中用于细分权限的标识符。我们可以为不同功能定义不同的Scope,然后给角色分配对应的Scope集合。
detect:run:执行手机检测任务。detect:history:查看检测历史记录。user:read:读取用户信息。user:write:创建、修改用户信息。log:read:查看系统日志。system:manage:管理系统服务(如重启应用)。
角色-Scope分配关系:
- 监考员:
[detect:run, detect:history] - 管理员:
[detect:run, detect:history, user:read, user:write, log:read, system:manage]
3.3 整体架构图
加了权限管理后,系统不再是简单的一个WebUI,而是变成了这样:
┌─────────────────┐ ┌──────────────────────────────────┐ │ │ │ │ │ 用户浏览器 │────▶│ 手机检测WebUI (Gradio) │ │ │ │ │ └─────────────────┘ └───────────────┬──────────────────┘ │ │ 携带Access Token访问 ▼ ┌─────────────────────────────────────────────────────────┐ │ OAuth2 授权服务器 │ │ ├── 用户认证 (登录) │ │ ├── 角色与权限校验 │ │ └── 颁发访问令牌 (Access Token) │ └─────────────────────────────────────────────────────────┘ │ │ 校验Token与权限 ▼ ┌─────────────────┐ ┌──────────────────────────────────┐ │ │ │ │ │ DAMO-YOLO │◀───│ 业务逻辑与模型API │ │ 检测模型 │ │ (根据权限决定是否处理请求) │ │ │ │ │ └─────────────────┘ └──────────────────────────────────┘工作流程:
- 用户打开WebUI,被重定向到登录页面。
- 用户输入账号密码,OAuth2服务器验证身份并确认其角色(管理员/监考员)。
- 服务器根据角色生成包含对应
scope的access_token,返回给WebUI。 - 此后,用户每次向WebUI发起请求(如上传图片),WebUI都会在后台携带这个
token去问授权服务器:“这个人能执行这个操作吗?” - 授权服务器校验
token有效性和scope,返回“允许”或“拒绝”。 - WebUI根据结果,决定是正常响应请求,还是返回一个“权限不足”的错误。
4. 代码实现详解
理论说完了,我们来看看具体怎么在现有的Gradio WebUI里加入这套东西。我们会用到authlib这个Python OAuth2库,它能让实现过程简单不少。
4.1 环境准备与依赖安装
首先,在原来的requirements.txt文件里添加新的依赖:
# 原有依赖 modelscope gradio>=4.0.0 torch opencv-python pillow supervisor # 新增权限管理依赖 authlib==1.3.0 # OAuth2服务器库 itsdangerous==2.2.0 # 用于生成和解析令牌 python-jose[cryptography]==3.3.0 # JWT令牌操作 passlib[bcrypt]==1.7.4 # 密码哈希加密然后安装它们:
pip install -r requirements.txt4.2 构建用户数据库
我们用一个简单的Python字典在内存里模拟用户数据库。在实际生产环境,你应该换成SQLite、MySQL或PostgreSQL。
创建一个新文件auth_server.py:
# auth_server.py - OAuth2授权服务器核心 from datetime import datetime, timedelta from typing import Optional, Dict import json from jose import JWTError, jwt from passlib.context import CryptContext # 模拟用户数据库 # 格式:username: {‘hashed_password‘, ‘role‘, ‘scopes‘} fake_users_db = { "admin": { "username": "admin", "full_name": "系统管理员", "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW", # 密码明文是"secret" "role": "admin", "scopes": ["detect:run", "detect:history", "user:read", "user:write", "log:read", "system:manage"], "disabled": False, }, "proctor_zhang": { "username": "proctor_zhang", "full_name": "张监考员", "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW", # 密码明文也是"secret" "role": "proctor", "scopes": ["detect:run", "detect:history"], "disabled": False, }, } # 密码加密上下文 pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") # JWT令牌配置 SECRET_KEY = "your-secret-key-please-change-this-in-production" # 生产环境务必更换! ALGORITHM = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES = 30 def verify_password(plain_password, hashed_password): """验证密码""" return pwd_context.verify(plain_password, hashed_password) def get_user(username: str): """从数据库获取用户""" if username in fake_users_db: user_dict = fake_users_db[username] return user_dict return None def authenticate_user(username: str, password: str): """用户认证""" user = get_user(username) if not user: return False if not verify_password(password, user["hashed_password"]): return False return user def create_access_token(data: dict, expires_delta: Optional[timedelta] = None): """创建JWT访问令牌""" 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_jwt4.3 集成OAuth2到Gradio WebUI
接下来,修改主应用文件(比如app.py),将登录和权限检查集成进去。
# app.py - 主应用文件(部分修改) import gradio as gr from auth_server import authenticate_user, create_access_token, SECRET_KEY, ALGORITHM from jose import JWTError, jwt from datetime import timedelta import json # 存储当前登录用户令牌的全局变量(简单示例,生产环境应用更安全的方式) current_user_token = None current_user_info = None def login(username: str, password: str): """登录函数,供Gradio界面调用""" global current_user_token, current_user_info user = authenticate_user(username, password) if not user: return "登录失败:用户名或密码错误", gr.update(visible=False) # 创建访问令牌 access_token_expires = timedelta(minutes=30) access_token = create_access_token( data={"sub": user["username"], "scopes": user["scopes"]}, expires_delta=access_token_expires, ) current_user_token = access_token current_user_info = user # 根据角色返回不同的欢迎信息和界面控制 welcome_msg = f"欢迎回来,{user['full_name']} ({user['role']})!" if user["role"] == "admin": return welcome_msg, gr.update(visible=True) # 管理员显示所有标签页 else: return welcome_msg, gr.update(visible=False) # 监考员隐藏管理标签页 def verify_token_and_scope(required_scope: str): """验证令牌和权限的装饰器(核心)""" def decorator(func): def wrapper(*args, **kwargs): global current_user_token, current_user_info if not current_user_token: return "错误:请先登录系统", None try: # 解码并验证JWT令牌 payload = jwt.decode(current_user_token, SECRET_KEY, algorithms=[ALGORITHM]) username: str = payload.get("sub") token_scopes = payload.get("scopes", []) if username is None: return "错误:无效的令牌", None # 检查权限范围 if required_scope not in token_scopes: return f"错误:权限不足。需要‘{required_scope}‘权限。", None # 权限验证通过,执行原函数 return func(*args, **kwargs) except JWTError: return "错误:令牌无效或已过期,请重新登录", None return wrapper return decorator # --- 原有检测函数,现在加上权限检查 --- @verify_token_and_scope("detect:run") def detect_phone(image): """执行手机检测(需要detect:run权限)""" # 这里是原有的DAMO-YOLO检测逻辑 # ... (你的检测代码) ... num_phones = 2 # 假设结果 confidence = 0.95 return processed_image, f"检测到 {num_phones} 部手机,平均置信度 {confidence:.1%}" # --- 构建Gradio界面,根据权限动态显示 --- with gr.Blocks(title="手机检测系统 - 权限管理版") as demo: gr.Markdown("# 📱 实时手机检测系统 - 多角色权限管理") # 登录界面 with gr.Row(): with gr.Column(scale=1): gr.Markdown("### 系统登录") login_username = gr.Textbox(label="用户名", placeholder="输入用户名") login_password = gr.Textbox(label="密码", placeholder="输入密码", type="password") login_button = gr.Button("登录") login_status = gr.Textbox(label="登录状态", interactive=False) with gr.Column(scale=2): # 登录后才显示的主功能区域 main_tabs = gr.Tabs(visible=False) # 初始隐藏 with main_tabs: # 标签页1:手机检测(监考员和管理员都能看) with gr.TabItem("手机检测", id="detect"): gr.Markdown("### 上传图片进行手机检测") input_image = gr.Image(label="上传图片", type="filepath") detect_button = gr.Button("开始检测", variant="primary") output_image = gr.Image(label="检测结果") result_text = gr.Textbox(label="检测信息") detect_button.click( fn=detect_phone, inputs=input_image, outputs=[output_image, result_text] ) # 标签页2:检测历史(监考员和管理员都能看,但数据范围不同) with gr.TabItem("检测历史", id="history"): gr.Markdown("### 历史检测记录") # ... 历史记录展示逻辑 ... # 标签页3:用户管理(仅管理员可见) with gr.TabItem("用户管理", id="user_manage", visible=False): gr.Markdown("### 用户管理面板") # 这个标签页的可见性会在登录后根据角色动态设置 # ... 用户管理逻辑 ... # 标签页4:系统日志(仅管理员可见) with gr.TabItem("系统日志", id="system_log", visible=False): gr.Markdown("### 系统运行日志") # ... 日志查看逻辑 ... # 登录按钮事件 login_button.click( fn=login, inputs=[login_username, login_password], outputs=[login_status, main_tabs] ) # 启动应用 if __name__ == "__main__": demo.launch(server_name="0.0.0.0", server_port=7860, share=False)4.4 前后端交互与令牌传递
在上面的简单示例中,我们把令牌存在了Python全局变量里,这仅适用于单用户、单会话的演示。真实场景中,Web前端(浏览器)和后端需要安全地传递令牌。
更安全的做法是:
- 前端登录:用户在前端登录,后端验证后返回
access_token。 - 前端存储:前端将
token安全存储(例如HttpOnly的Cookie,或localStorage但需防范XSS)。 - 请求携带:前端每次调用Gradio后端接口时,在HTTP请求头中携带令牌:
Authorization: Bearer <your_access_token> - 后端校验:Gradio后端(或一个独立的API网关/中间件)拦截请求,从
Authorization头中取出token,调用我们写的verify_token_and_scope函数进行校验。
这样,权限校验的逻辑就和业务逻辑(手机检测)解耦了,更加清晰和安全。
5. 部署与配置指南
代码写好了,我们来看看怎么把它和原来的手机检测系统一起部署起来。
5.1 项目结构更新
部署前,你的项目目录应该看起来像这样:
/root/phone-detection/ ├── app.py # 主Gradio应用(已集成权限) ├── auth_server.py # OAuth2认证服务器逻辑 ├── requirements.txt # 依赖列表(已更新) ├── start.sh # 启动脚本 ├── stop.sh # 停止脚本 ├── models/ # DAMO-YOLO模型文件 │ └── damo_yolo_s.pth ├── logs/ # 日志目录 │ ├── access.log │ └── error.log └── config/ # 新增配置目录 ├── users.yaml # 用户配置文件(替代fake_db) └── oauth_config.yaml # OAuth2服务器配置5.2 启动脚本修改
更新start.sh脚本,确保服务启动时加载所有模块:
#!/bin/bash # start.sh - 启动手机检测WebUI服务(带权限管理) echo "启动手机检测WebUI服务(带权限管理)..." echo "当前目录: $(pwd)" # 激活Python虚拟环境(如果你有的话) # source /path/to/venv/bin/activate # 安装依赖(首次运行或依赖有更新时) echo "检查并安装Python依赖..." pip install -r requirements.txt -q # 启动Gradio应用 echo "启动主应用服务器..." python app.py & # 记录进程ID(可选,用于管理) echo $! > /tmp/phone_detection_ui.pid echo "服务启动完成!" echo "访问地址: http://$(hostname -I | awk '{print $1}'):7860" echo "使用Ctrl+C停止服务"5.3 Supervisor配置更新
如果你用Supervisor管理服务,需要更新配置文件,确保环境变量和路径正确。
; /etc/supervisor/conf.d/phone-detection-oauth.conf [program:phone-detection-oauth] command=/usr/bin/python3 /root/phone-detection/app.py directory=/root/phone-detection user=root autostart=true autorestart=true stopasgroup=true killasgroup=true stderr_logfile=/root/phone-detection/logs/error.log stdout_logfile=/root/phone-detection/logs/access.log environment=SECRET_KEY="your-production-secret-key-change-me",PYTHONPATH="/root/phone-detection"更新配置后,重启Supervisor并启动新服务:
# 重新加载Supervisor配置 sudo supervisorctl reread sudo supervisorctl update # 启动新服务 sudo supervisorctl start phone-detection-oauth # 查看状态 sudo supervisorctl status phone-detection-oauth6. 使用体验与效果
部署完成后,我们来看看不同角色的用户会看到什么。
6.1 监考员视角
- 登录:监考员
proctor_zhang使用自己的账号登录。 - 界面:登录后,他只能看到两个标签页:“手机检测”和“检测历史”。
- 操作:
- 在“手机检测”页面上传考场图片,系统自动标记出手机。
- 在“检测历史”页面,只能查看自己账号下的检测记录。
- 限制:他完全看不到“用户管理”和“系统日志”标签页。如果他试图通过直接输入URL访问管理页面,后端权限校验会拦截并返回“权限不足”错误。
6.2 管理员视角
- 登录:管理员
admin登录。 - 界面:登录后,他看到完整的四个标签页:“手机检测”、“检测历史”、“用户管理”、“系统日志”。
- 操作:
- 拥有监考员的所有功能。
- 可以在“用户管理”中新增、禁用监考员账号。
- 可以在“系统日志”中查看所有用户的操作记录和系统运行状态。
- 可以重启后端检测服务(如果实现了该功能)。
6.3 实际效果对比
| 功能模块 | 监考员 (Proctor) | 管理员 (Admin) |
|---|---|---|
| 手机检测 | ✅ 完全可用 | ✅ 完全可用 |
| 查看检测历史 | ✅ 仅看本人记录 | ✅ 查看所有记录 |
| 用户管理 | ❌ 不可见/无权限 | ✅ 增删改查用户 |
| 查看系统日志 | ❌ 不可见/无权限 | ✅ 查看完整日志 |
| 服务管理 | ❌ 无权限 | ✅ 重启、停止服务 |
7. 总结
给手机检测WebUI加上基于OAuth2的权限管理,听起来复杂,但拆解开来就是“谁(用户)能用什么(Scope)功能”的问题。我们通过今天的设计和实现,达到了几个关键目标:
- 职责分离:监考员专注监考任务,管理员负责系统维护,权责清晰。
- 安全提升:避免了未授权访问和越权操作的风险。令牌机制也避免了密码在网络上频繁传输。
- 可扩展性强:OAuth2的
scope设计非常灵活。如果未来需要增加“巡考员”角色(只能看记录,不能检测),或者增加新的功能模块(如“报表导出”),只需要定义新的scope并分配给对应角色即可,核心架构不用大改。 - 用户体验良好:用户登录后,界面会根据其权限动态呈现,无需看到用不到的功能,简洁高效。
下一步可以优化的方向:
- 持久化存储:将用户数据库从内存字典迁移到SQLite或MySQL。
- 更精细的权限:在
scope基础上,实现数据行级的权限控制(例如,监考员只能查看自己考场的记录)。 - 独立的授权服务器:将
auth_server.py拆分成一个独立的微服务,供多个系统共用。 - 前端路由守卫:在前端(如果使用更复杂的前端框架)也加入权限检查,隐藏无权限的菜单或按钮,提供更好的交互反馈。
希望这篇从零到一的指南,能帮你为你的手机检测系统,乃至其他需要权限控制的内部工具,构建一个清晰、安全、专业的访问控制层。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。
