「产品经理用 Claude 实现产品」系列 · 第14篇
你写过无数次"角色权限矩阵"——管理员全权限、普通用户管自己的、访客只读。这些设计以前你只能交给开发实现。这一篇,用 Claude 把你的权限矩阵变成真实代码:完整的登录注册、JWT 认证、三种角色、后端鉴权、前端按钮权限控制。
一、前言:角色权限,产品经理的必修课
作为产品经理,你肯定做过这样一张表:
| 查看需求列表 | ✅ | ✅ | ✅ |
| 创建需求 | ✅ | ✅ | ❌ |
| 编辑自己的需求 | ✅ | ✅ | ❌ |
| 编辑他人需求 | ✅ | ❌ | ❌ |
| 删除需求 | ✅ | 仅自己创建的 | ❌ |
| 管理用户 | ✅ | ❌ | ❌ |
这就是角色权限矩阵。你写了多少次,你把它交给开发,他们实现了多少次。
今天,用 Claude 自己来实现它。
你会发现:你对这个功能的理解,比任何人都深。 因为你写了无数次,你知道每个格子里的逻辑,你只是从来没有自己实现过而已。
💡 本系列全程使用 weelinking 访问 Claude,国内可稳定使用
二、用户注册与登录
2.1 注册功能完善
第11篇我们实现了基础的注册接口,现在完善前端和体验细节:
你: 帮我完善注册页面(src/pages/Register.jsx):
表单字段:
- 用户名:必填,4-20个字符,只能是字母/数字/下划线,实时校验
- 密码:必填,最少8位,至少包含数字和字母,显示密码强度条
- 确认密码:必填,和密码一致
- 邮箱:选填,格式校验
注册成功后:
- 显示成功提示:“注册成功!欢迎加入需求管理平台”
- 3秒后自动跳转登录页(显示倒计时)
注册失败处理:
- 用户名已存在:在用户名字段下显示"该用户名已被使用"
- 其他错误:顶部错误提示
密码强度条——这是个你在很多产品里见过的体验细节,Antd 有现成组件,Claude 帮你集成。
2.2 登录功能完善
你: 完善登录页面,在现有基础上增加:
- "记住我"功能:勾选后,下次打开自动登录(token 存入 localStorage,不勾选存 sessionStorage)
- 登录失败区分提示:
- 用户名不存在:“用户名不存在,去注册?”("去注册"是链接)
- 密码错误:“密码错误,还剩 X 次尝试机会”
- 连续失败5次:锁定账号1分钟,显示倒计时
- 忘记密码链接(暂时跳转到一个"请联系管理员"的提示页)
“连续失败锁定”——这是安全措施,也是你在设计登录功能时一定会考虑到的。现在 Claude 帮你实现了:
// 后端:登录失败记录
const loginAttempts = {}; // 临时方案,生产环境用数据库/Redis
const login = (req, res) => {
const { username, password } = req.body;
const key = username;
// 检查是否被锁定
if (loginAttempts[key] && loginAttempts[key].lockUntil > Date.now()) {
const remaining = Math.ceil((loginAttempts[key].lockUntil – Date.now()) / 1000);
return res.status(429).json({
success: false,
message: `账号已锁定,请 ${remaining} 秒后再试`
});
}
const user = db.prepare('SELECT * FROM users WHERE username = ?').get(username);
if (!user) {
return res.status(401).json({ success: false, message: '用户名不存在' });
}
const valid = bcrypt.compareSync(password, user.password);
if (!valid) {
// 记录失败次数
if (!loginAttempts[key]) loginAttempts[key] = { count: 0 };
loginAttempts[key].count++;
const remaining = 5 – loginAttempts[key].count;
if (loginAttempts[key].count >= 5) {
loginAttempts[key].lockUntil = Date.now() + 60 * 1000; // 锁定1分钟
return res.status(429).json({ success: false, message: '密码错误次数过多,账号已锁定1分钟' });
}
return res.status(401).json({
success: false,
message: `密码错误,还剩 ${remaining} 次尝试机会`
});
}
// 登录成功,清除失败记录
delete loginAttempts[key];
const token = jwt.sign({ id: user.id, username: user.username, role: user.role }, JWT_SECRET, { expiresIn: '7d' });
res.json({ success: true, data: { token, user: { id: user.id, username: user.username, role: user.role } } });
};
2.3 退出登录
你: 实现退出登录功能:
- 点击右上角用户头像,下拉菜单里有"退出登录"选项
- 点击后弹出确认框(“确定要退出登录吗?”)
- 确认后:清除 localStorage 里的 token 和 user 信息,跳转登录页
退出登录是纯前端操作——清除 token 就行。不需要调后端接口(当然加上更好,但对于我们的场景,前端处理就够了)。
三、角色与权限
3.1 定义角色体系
我们的产品有三种角色,把它们精确定义出来:
你: 帮我在后端定义角色权限体系,在 src/middleware/auth.js 里创建权限中间件。
三种角色:
- admin(管理员):所有操作权限
- user(普通用户):可以创建需求、编辑/删除自己创建的需求、查看所有需求
- guest(访客):只能查看需求,不能创建/编辑/删除
权限中间件:
Claude 会生成:
// src/middleware/auth.js
const jwt = require('jsonwebtoken');
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
// 1. 验证 token
const authenticate = (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ success: false, message: '未登录,请先登录' });
}
try {
const decoded = jwt.verify(token, JWT_SECRET);
req.user = decoded; // { id, username, role }
next();
} catch (err) {
return res.status(401).json({ success: false, message: 'Token 已过期,请重新登录' });
}
};
// 2. 要求特定角色
const requireRole = (roles) => (req, res, next) => {
if (!roles.includes(req.user.role)) {
return res.status(403).json({ success: false, message: '权限不足' });
}
next();
};
// 3. 要求是创建者或管理员
const requireOwnerOrAdmin = (getResourceUserId) => async (req, res, next) => {
if (req.user.role === 'admin') return next(); // 管理员直接放行
const resourceUserId = await getResourceUserId(req);
if (req.user.id !== resourceUserId) {
return res.status(403).json({ success: false, message: '只能操作自己创建的内容' });
}
next();
};
module.exports = { authenticate, requireRole, requireOwnerOrAdmin };
3.2 在接口上应用权限
有了权限中间件,把它加到各个接口上:
你: 帮我给 requirements 路由加上权限控制:
- GET /api/requirements(获取列表):所有登录用户可以访问,包括 guest
- POST /api/requirements(创建):需要是 user 或 admin,guest 不行
- GET /api/requirements/:id(获取详情):所有登录用户
- PUT /api/requirements/:id(更新):需要是资源创建者或 admin
- DELETE /api/requirements/:id(删除):需要是资源创建者或 admin
Claude 会修改路由文件:
// src/routes/requirements.js
const express = require('express');
const router = express.Router();
const { authenticate, requireRole, requireOwnerOrAdmin } = require('../middleware/auth');
const controller = require('../controllers/requirementController');
// 所有路由需要登录
router.use(authenticate);
// 查看:所有角色
router.get('/', controller.getRequirements);
router.get('/:id', controller.getRequirementById);
// 创建:user 和 admin
router.post('/', requireRole(['user', 'admin']), controller.createRequirement);
// 更新/删除:创建者或 admin
const getRequirementOwner = (req) => {
const req_db = require('../models/db');
const item = req_db.prepare('SELECT createdBy FROM requirements WHERE id = ?').get(req.params.id);
return item?.createdBy;
};
router.put('/:id', requireOwnerOrAdmin(getRequirementOwner), controller.updateRequirement);
router.delete('/:id', requireOwnerOrAdmin(getRequirementOwner), controller.deleteRequirement);
module.exports = router;
这就是你的权限矩阵的代码实现。 看着这个路由文件,再对照你的权限矩阵——完全一一对应。
3.3 管理员管理用户
你: 帮我实现用户管理接口,只有 admin 能访问:
- GET /api/users(获取用户列表)
- PUT /api/users/:id/role(修改用户角色):只能改成 user 或 guest,不能改成 admin(避免意外)
- DELETE /api/users/:id(删除用户):不能删除自己,不能删除其他 admin
四、前端权限控制
后端已经拦截了非法操作,但用户体验上,没权限的按钮最好直接不显示,而不是点击后报错。
4.1 获取并存储当前用户信息
登录成功后,把用户信息(包括角色)存起来,全局可用:
你: 帮我创建用户信息的全局状态管理(用 React Context),在 src/contexts/AuthContext.jsx:
- 存储:user(用户信息,含 role)、token
- 登录后设置,退出后清空
- 提供 useAuth() 钩子,在任何组件里都能获取当前用户信息和角色
- 页面刷新后从 localStorage 恢复状态
Claude 会生成 AuthContext,所有组件都能用 useAuth() 获取当前用户角色。
4.2 权限控制组件
你: 帮我创建一个权限控制组件 src/components/PermissionGuard.jsx:
- 用法:<PermissionGuard roles={['admin', 'user']}><CreateButton /></PermissionGuard>
- 当前用户角色在 roles 数组内,显示子元素
- 不在的话,不渲染(或显示 fallback 内容)
另外,用法:<PermissionGuard ownerOnly requirementId={id}><EditButton /></PermissionGuard>
- ownerOnly 模式:只有需求创建者或 admin 才显示
// src/components/PermissionGuard.jsx
import { useAuth } from '../contexts/AuthContext';
const PermissionGuard = ({ roles, ownerOnly, createdBy, children, fallback = null }) => {
const { user } = useAuth();
if (!user) return fallback;
// 角色权限检查
if (roles && !roles.includes(user.role)) return fallback;
// 创建者权限检查
if (ownerOnly && user.role !== 'admin' && user.id !== createdBy) return fallback;
return children;
};
export default PermissionGuard;
4.3 在页面里应用权限控制
把 PermissionGuard 加到需要控制的地方:
你: 在需求列表页,用 PermissionGuard 控制:
- "创建需求"按钮:只有 user 和 admin 显示
- "编辑"操作:只有创建者和 admin 显示
- "删除"操作:只有创建者和 admin 显示
在需求详情页:
- "编辑"按钮:创建者或 admin
- "删除"按钮:创建者或 admin
- 状态修改:user 和 admin
在侧边菜单:
- "用户管理"菜单项:只有 admin 显示
改造后,以访客身份登录,你会发现"创建需求"按钮不见了,没有任何编辑删除操作——这就是你设计的权限矩阵,真实地运行在产品里。
4.4 路由权限保护
你: 帮我给前端路由加上权限保护:
- 未登录用户访问任何页面,自动重定向到 /login
- 已登录用户访问 /login,自动重定向到 /home
- 访客访问 /requirements/new(创建需求页),重定向到 /requirements 并提示"无权创建需求"
Claude 会创建 PrivateRoute 组件:
// src/components/PrivateRoute.jsx
import { Navigate } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';
const PrivateRoute = ({ children, roles }) => {
const { user, token } = useAuth();
// 未登录,跳登录页
if (!token) return <Navigate to="/login" replace />;
// 角色不够,跳列表页
if (roles && !roles.includes(user.role)) {
return <Navigate to="/requirements" replace />;
}
return children;
};
路由配置:
<Route path="/requirements/new" element={
<PrivateRoute roles={['user', 'admin']}>
<RequirementForm />
</PrivateRoute>
} />
五、安全基础
权限控制不只是"显不显示按钮",还有真正的安全防线。Claude 帮你处理的安全细节:
5.1 密码存储安全
你: 确认密码存储方案是否安全。
Claude 会解释:我们用的 bcryptjs 是业界标准的密码哈希库,密码不是加密,而是单向哈希——存的是哈希值,无法反向得到原始密码。这是正确的做法。
// 注册时:哈希密码后存储
const hashedPassword = bcrypt.hashSync(password, 10); // 10 是计算强度
// 登录时:验证密码
const valid = bcrypt.compareSync(inputPassword, hashedPassword);
无论是你的数据库被盗,还是开发人员查数据库,都不会看到用户的真实密码。 这是基础安全红线。
5.2 JWT Token 安全
你: 帮我检查 JWT 实现是否存在安全问题,并给出改进建议。
Claude 会指出几个需要注意的地方:
问题1:JWT Secret 写死在代码里
// ❌ 不安全
const JWT_SECRET = 'your-secret-key';
// ✅ 改用环境变量
const JWT_SECRET = process.env.JWT_SECRET;
问题2:Token 过期时间太长
7天的 Token 意味着:用户密码被改了,旧 Token 还能用7天。可以缩短为1天,或实现 Token 刷新机制。我们暂时保持7天,后续优化。
问题3:没有 Token 黑名单
用户退出登录后,Token 技术上还有效(除非过期)。进阶方案是维护一个 Token 黑名单(Redis 存储),但对我们的场景,前端删除 Token 后用户就无法再使用,影响有限。
你: 帮我创建 .env 文件模板,和 .env.example,把 JWT_SECRET 改成从环境变量读取。
# .env(不提交 git)
JWT_SECRET=your-very-long-random-secret-key
PORT=3001
# .gitignore
.env
// 读取环境变量
require('dotenv').config();
const JWT_SECRET = process.env.JWT_SECRET;
if (!JWT_SECRET) {
throw new Error('JWT_SECRET 环境变量未设置!');
}
5.3 常见安全问题防范
让 Claude 帮你做一次安全检查:
你: 帮我检查当前后端代码,是否存在以下安全问题,并给出修复建议:
Claude 会逐一检查并给出建议。常见问题:
| SQL 注入 | 用 prepared statements,安全 | ✅ 已处理 |
| XSS | 存储了 description 字段 | 前端渲染时用 DOMPurify 过滤 |
| 接口限流 | 未做 | 加 express-rate-limit |
| 敏感信息 | 错误可能暴露 SQL | 生产环境只返回通用错误消息 |
简单加一个限流:
你: 帮我给登录接口加上 IP 限流:同一个 IP,每15分钟最多尝试20次。使用 express-rate-limit 库。
const rateLimit = require('express-rate-limit');
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
max: 20, // 最多20次
message: { success: false, message: '请求过于频繁,请15分钟后再试' }
});
router.post('/login', loginLimiter, authController.login);
六、验收权限功能
权限系统做完后,需要专项测试。这类功能最容易有漏洞——后端拦截了,但前端没拦;或者前端不显示,但后端没验证,直接调 API 还是能操作。
6.1 权限测试矩阵
注册三个测试账号,分别设置三种角色(在数据库里手动改 role 字段):
| admin_test | admin | test123456 |
| user_test | user | test123456 |
| guest_test | guest | test123456 |
逐一验证每种角色的操作权限:
以 guest_test 登录:
□ 能看到需求列表(✅ 应该能)
□ 看不到"创建需求"按钮(✅ 应该看不到)
□ 看不到任何"编辑"、"删除"按钮
□ 直接访问 /requirements/new,被重定向
□ 用 Apifox 直接调用 POST /api/requirements,带 guest token,返回 403
以 user_test 登录:
□ 能创建需求
□ 能编辑自己创建的需求
□ 看不到别人需求的"编辑"按钮
□ 用 Apifox 直接调用 PUT /api/requirements/他人需求ID,返回 403
以 admin_test 登录:
□ 能看到所有操作按钮
□ 能编辑任何人的需求
□ 能删除任何需求
□ 能看到"用户管理"菜单
□ 能修改用户角色
关键测试:后端也要拦截
即使前端不显示按钮,用 Apifox 直接发请求,后端也必须拒绝非法操作。这才是真正的安全——不能依赖"用户看不到按钮就不会点"。
七、完整登录流程体验
完成本篇后,完整的登录体验应该是这样的:
这就是一个完整的、安全的认证流程。
八、阶段成果
完成本篇后:
- ✅ 完整注册流程(密码强度、校验、成功跳转)
- ✅ 完整登录流程(记住我、失败限制、锁定保护)
- ✅ JWT 认证(环境变量管理 Secret)
- ✅ 三种角色定义(admin/user/guest)
- ✅ 后端接口鉴权(未登录401、无权限403)
- ✅ 前端权限控制(PermissionGuard 组件、路由保护)
- ✅ 基础安全处理(密码哈希、限流、SQL 注入防范)
你的"需求管理平台"现在是一个有完整权限体系的系统了。 不同角色,看到不同的界面,有不同的操作权限——这就是你在 PRD 里设计的,现在实现了。
git add .
git commit -m "完成用户认证和角色权限系统"
git push
九、总结与下期预告
🎯 本篇核心要点
1. 角色权限矩阵 = 中间件 + 路由配置。 你在 PRD 里画的那张表,在代码里就是这两个东西。一旦理解了这个对应关系,"实现权限"就不神秘了。
2. 前端不显示 ≠ 后端安全。 真正的权限控制在后端。前端不显示按钮只是用户体验,后端才是真正的防线。两者都要做。
3. 密码安全是底线。 任何真实产品,密码必须哈希存储,绝不能明文。bcrypt 是标准方案,直接用。
4. 安全问题用 Claude 做检查。 你不需要精通安全,但要知道有哪些常见漏洞,用 Claude 帮你检查和修复。这个习惯很重要。
5. 权限系统要做专项测试。 每种角色、每个操作,都要测一遍。权限漏洞是最严重的产品问题之一。
📌 记住这句话
你写过无数张角色权限矩阵,今天第一次自己把它变成了代码。下次跟开发说"这个权限控制很复杂",你心里已经知道它到底复杂在哪里。这就是"技术型 PM"的价值。
📣 下期预告
第15篇:《UI/UX 打磨——产品经理的审美终于能自己实现》
核心功能全部完成!下一篇,我们来提升产品的"颜值"和体验——这是产品经理最擅长的领域。
你会做到:
- 整体配色方案优化
- 加载状态(骨架屏、Loading)
- 操作反馈(Toast 提示、按钮状态)
- 空状态设计
- 页面切换动画
- 响应式适配
产品经理的审美,终于能自己实现了,不用再跟设计师解释"我要的是这个感觉"。
💡 本系列全程使用 weelinking 访问 Claude,国内可稳定使用
📎 配套资源
📋 角色权限矩阵模板
【需求管理平台角色权限矩阵】
功能模块 | admin | user | guest
————|——-|——|——
需求 – 查看列表 | ✅ | ✅ | ✅
需求 – 查看详情 | ✅ | ✅ | ✅
需求 – 创建 | ✅ | ✅ | ❌
需求 – 编辑自己的 | ✅ | ✅ | ❌
需求 – 编辑他人的 | ✅ | ❌ | ❌
需求 – 删除自己的 | ✅ | ✅ | ❌
需求 – 删除他人的 | ✅ | ❌ | ❌
需求 – 归档管理 | ✅ | ❌ | ❌
用户 – 查看列表 | ✅ | ❌ | ❌
用户 – 修改角色 | ✅ | ❌ | ❌
用户 – 删除用户 | ✅ | ❌ | ❌
📋 认证与权限检查清单
□ 注册功能
□ 用户名唯一性验证
□ 密码强度校验(长度、包含数字字母)
□ 密码哈希存储(bcrypt)
□ 注册成功跳转
□ 登录功能
□ JWT token 生成
□ token 存储(localStorage/sessionStorage)
□ 登录失败计数和锁定
□ 记住我功能
□ 认证中间件
□ token 验证(authenticate)
□ 角色验证(requireRole)
□ 创建者验证(requireOwnerOrAdmin)
□ 后端接口鉴权
□ 需要登录的接口都加了 authenticate
□ 需要特定角色的接口加了 requireRole
□ 需要创建者权限的接口加了 requireOwnerOrAdmin
□ 前端权限控制
□ 创建 AuthContext,全局管理用户状态
□ 创建 PermissionGuard 组件
□ 按钮/操作根据角色显示/隐藏
□ 路由保护(未登录重定向)
□ 页面刷新后恢复登录状态
□ 安全处理
□ JWT_SECRET 从环境变量读取
□ 创建 .env 文件,加入 .gitignore
□ 登录接口加限流
□ 密码不在响应中返回
□ 权限测试
□ 三种角色分别测试
□ 后端接口直接调用测试(验证后端真的拦截)
📋 常用提示词模板
实现权限控制:
帮我给 [接口/功能] 加上权限控制:
角色权限要求:
– admin:[能做什么]
– user:[能做什么]
– guest:[能做什么]
特殊规则:
– [规则1:比如创建者才能编辑自己的内容]
后端:修改路由配置,加上对应中间件
前端:用 PermissionGuard 组件控制 [哪些按钮/操作]
安全检查:
帮我检查这段代码的安全问题:
[粘贴代码]
重点关注:
1. 是否有 SQL 注入风险
2. 用户输入是否有过滤/验证
3. 是否有敏感信息泄露
4. 权限是否真的在后端校验
请给出具体的安全建议和修复方案。
🌟 如果这篇文章对你有帮助,请点赞👍 收藏⭐ 关注🔔
你的支持是我持续更新的最大动力!
💬 评论区聊聊:你之前设计的权限矩阵里,有没有遇到过开发说"这个实现不了"?看完这篇,你觉得那个"实现不了"是真的吗?
📌 系列导航: 产品经理用 Claude 实现产品 · 系列目录
⏪ 上一篇: 第13篇:核心功能实现——需求的增删改查全流程
⏩ 下一篇: 第15篇:UI/UX 打磨——产品经理的审美终于能自己实现
💡 本系列全程使用 weelinking 访问 Claude,国内可稳定使用 🚀 整个系列的核心理念:你不需要变成程序员,你只需要从"找人做"变成"自己能做"。



