Web3 角色权限管理系统
系统概述
基于 Solidity 智能合约的角色权限管理系统。该系统通过 Web Dashboard 和后端服务实现超级管理员(super admin)对运营管理员的完整生命周期管理:包括新建账号、自动生成关联钱包、链上授权、日常操作代理签名,以及离职时的权限撤销和账号删除。
核心目标
- 实现运营管理员的无感钱包管理(无需自行保管私钥)。
- 确保 Dashboard 操作通过具有对应权限的链上地址执行,实现可追溯性。
- 覆盖管理员从入职到离职的全流程自动化与安全控制。
- 降低私钥管理风险,防止未授权操作。
前提假设
- 智能合约采用基于角色的访问控制(RBAC),推荐使用 OpenZeppelin 的 AccessControl 库(例如 ADMIN_ROLE、OPERATOR_ROLE 等角色)。
- 后端使用 Node.js + ethers.js(或 web3.js)与以太坊兼容链交互。
- Dashboard 为 Web 应用(例如 React),支持用户名/密码登录。
- 项目运行在以太坊主网或兼容链上。
- 超级管理员拥有合约部署权限及其钱包地址已被设为 DEFAULT_ADMIN_ROLE。
系统架构
主要组件
-
Dashboard(前端)
- 超级管理员创建/删除运营账号的界面。
- 运营管理员执行合约操作的界面(如参数调整、暂停合约等)。
- 负责用户名/密码认证及会话管理。
-
后端服务(Backend)
- 处理 Dashboard 的 API 请求。
- 管理用户数据库(用户名、密码哈希、关联钱包地址)。
- 负责生成以太坊钱包(公私钥对)。
- 使用存储的私钥代理签名并调用合约。
- 与区块链 RPC 节点交互(推荐 Infura/Alchemy)。
-
数据库
- 存储用户凭证、钱包关联关系、审计日志。
- 推荐 PostgreSQL 或 MongoDB,敏感字段需加密存储。
-
Solidity 智能合约
- 实现 RBAC,支持 grantRole / revokeRole 方法。
- 关键功能添加 onlyRole 修饰符限制调用者。
- 由超级管理员部署并初始化。
-
区块链节点提供商
- 提供 RPC 接口,用于交易广播和合约查询。
数据流概览
- 账号创建:Dashboard → 后端(生成钱包 → 存储关联 → 超级管理员钱包调用合约授权)。
- 日常操作:Dashboard → 后端(验证身份 → 使用关联钱包签名 → 调 用合约)。
- 账号删除:Dashboard → 后端(超级管理员钱包调用合约撤销权限 → 删除数据库记录)。
管理员生命周期流程
1. 新管理员入职(招聘)
-
触发:超级管理员在 Dashboard 创建新账号(用户名:admin1,密码:xxx)。
-
详细步骤:
- Dashboard 调用后端 API:POST /users 携带
{username, password}。 - 后端校验用户名唯一性。
- 后端生成新以太坊钱包(例如 ethers.Wallet.createRandom()),得到地址 0xAA... 和私钥。
- 后端将数据存入数据库:
{username, hashed_password, wallet_address, encrypted_private_key}。 - 后端使用超级管理员钱包向 0xAA... 转账 m 个 Gas 代币
- m 可配置(建议初始值 0.5~2 个主网代币,根据链上 Gas 价格和预期操作频率调整)。
- 转账交易由超级管理员钱包签名并广播。
- 等待交易确认。
- 记录转账交易哈希到数据库和审计日志(字段:gas_transfer_out_tx)。
- 后端使用超级管理员钱包签名,调用合约 grantRole(OPERATOR_ROLE, 0xAA...)。
- 等待交易上链确认。
- 返回成功响应给 Dashboard(仅显示用户名和地址,显示钱包地址、初始 Gas 余额、转账交易哈希, 不暴露私钥)。
- Dashboard 调用后端 API:POST /users 携带
-
异常处理:
- 交易失败:重试或通知超级管理员。
- 用户名重复:拒绝创建。
- 钱包生成失败:记录日志并重试。
- Gas 转账失败:回滚整个创建流程(不存储用户记录、不授权),通知超级管理员检查余额或网络。
- 授权交易失败:已转账的 Gas 暂时滞留,可手动归集或重试授权。
- 建议:将 Gas 转账放在授权之前,确保即使授权失败也能手动回收。
2. 管理员日常操作
-
触发:运营管理员登录 Dashboard 并点击某合约操作按钮。
-
详细步骤:
- Dashboard 验证会话有效性。
- Dashboard 调用后端 API(如 POST /operations/updateParam)并传递参数。
- 后端根据会话获取用户记录及关联钱包。
- 后端解密私钥,构造签名者(new ethers.Wallet(privateKey, provider))。
- 后端调用合约对应方法并签名交易。
- 广播交易并监控结果。
- 将交易哈希或结果返回给 Dashboard。
- 可选补充:在运营管理员执行合约操作前,后端可检查关联地址 Gas 余额。
- 如果余额低于阈值(例如 < 0.1 个代币),自动从超级管理员钱包补充一定数量 Gas。
- 补充交易记录到日志(gas_top_up_tx)。
-
异常处理:
- 会话失效:跳转登录页。
- 链上权限不足:返回错误(理论上不应发生)。
- Gas 费用过高:预估 Gas 并可让用户确认。
3. 管理员离职(删除账号)
-
触发:超级管理员在 Dashboard 删除某账号。
-
详细步骤:
- Dashboard 调用后端 API:DELETE /users/admin1。
- 后端验证操作人为超级管理员。
- 查询该用户关联的钱包地址。
- 归集剩余 Gas 代币
- 后端解密该钱包私钥。
- 查询 0xAA... 当前原生代币余额。
- 如果余额 > 最小保留值(例如 0.01 个代币,用于防止尘埃攻击),则构造转账交易:
- 发送方:0xAA...(使用其私钥签名)。
- 接收方:超级管理员指定的资金池地址(可配置为超级管理员钱包或专用归集地址)。
- 金额:balance - 保留值(或全部发送,设置高 Gas Limit 确保成功)。
- 广播归集交易,等待确认。
- 记录归集交易哈希(gas_collect_tx)。
- 后端使用超级管理员钱包签名,调用合约 revokeRole(OPERATOR_ROLE, 0xAA...)。
- 等待交易确认成功后,再删除数据库记录(含加密私钥)。
- 返回成功响应。
-
异常处理:
- 撤销交易失败:不删除数据库记录,避免链上残留权限。
- 存在活跃会话:强制失效该用户所有会话。
安全注意事项
-
私钥管理
- 私钥绝不在前端或日志中暴露。
- 数据库中私钥必须加密存储(推荐 AES + 主密钥存放于环境变量或秘密管理服务,如 AWS Secrets Manager)。
- 生产环境建议使用 HSM(硬件安全模块) 避免软件明文存储。
- 风险点:后端被攻破可能导致所有私钥泄露,需严格控制服务器访问权限。
-
配置化 Gas 管理
- 在后端配置文件或数据库中设置:
- 初始转账金额 initial_gas_amount
- 低余额补充阈值 low_balance_threshold
- 补充金额 top_up_amount
- 归集保留最小值 min_reserve_balance
- 归集目标地址 collect_to_address
- 在后端配置文件或数据库中设置:
-
身份认证
- 密码使用强哈希算法(bcrypt 或 Argon2)。
- 采用 JWT 或 Session + 短过期时间。
- 登录接口增加速率限制防暴力破解。
- 建议开启 2FA(双因素认证)。
-
合约安全
- grantRole / revokeRole 必须限制为 onlyRole(DEFAULT_ADMIN_ROLE)。
- 合约需经过专业审计,防范重入等常见漏洞。
- 生产环境超级管理员操作建议升级为多签钱包(Gnosis Safe)。
-
审计日志
- 记录所有关键操作(创建、删除、链上调用),包含操作人、时间、交易哈希。
- 日志建议使用不可篡改存储(如追加式数据库或链上事件)。
-
其他风险
- 钓鱼攻击:管理员需注意 Dashboard 域名安全。
- 私钥轮换:建议定期为活跃管理员重新生成钱包并更新权限。
- 合规性:根据项目所在辖区,可能需要对管理员进行 KYC。
实现技术建议(伪代码)
// 创建用户 - Gas 转账 + 授权
const wallet = ethers.Wallet.createRandom();
const address = wallet.address;
// 存储加密私钥...
// Step 1: 转账 Gas
const superSigner = new ethers.Wallet(process.env.SUPER_ADMIN_PK, provider);
const gasAmount = ethers.parseEther("1.0"); // 可配置 m 个
const transferTx = await superSigner.sendTransaction({
to: address,
value: gasAmount,
});
await transferTx.wait();
console.log("Gas transfer tx:", transferTx.hash);
// Step 2: 授权角色
const contract = new ethers.Contract(CONTRACT_ADDR, ABI, superSigner);
const grantTx = await contract.grantRole(OPERATOR_ROLE, address);
await grantTx.wait();
// 删除用户 - 归集 + 撤销
const userWallet = new ethers.Wallet(decryptedPrivateKey, provider);
const balance = await provider.getBalance(address);
if (balance > ethers.parseEther("0.01")) {
const collectTx = await userWallet.sendTransaction({
to: process.env.COLLECT_ADDRESS,
value: balance - ethers.parseEther("0.005"), // 保留一点 Gas 给交易本身
gasLimit: 21000,
gasPrice: await provider.getGasPrice(),
});
await collectTx.wait();
}
// 撤销角色(superSigner)
const revokeTx = await contract.revokeRole(OPERATOR_ROLE, address);
await revokeTx.wait();
// 最后删除 DB 记录