09 - 鉴权与权限及状态码
核心概念1. 密码绝不能明文入库客户端明文 password → bcrypt.hash(password, 10) // 注册时 → 数据库存 $2b$10$... 哈希 登录时 → bcrypt.compare(明文, 库中哈希) // 不 decrypt只比对2. JWT 流程登录成功 → jwt.sign({ userId, email }, JWT_SECRET, { expiresIn: 7d }) → 返回 token 给客户端 后续请求 → Header: Authorization: Bearer token → jwt.verify(token, JWT_SECRET) → 得到 userId挂到 req.userIdJWT payload 放什么userId必须、email可选。绝不放 password。JWT_SECRET放.env不写死在代码里不提交 Git。3. 不信任客户端传的 userId改前POST /api/todosbody 里传userId→ 可伪造他人身份改后userId只从 token 解析的req.userId取4. HTTP 状态码状态码分三大类客户端据此决定下一步动作重试、跳转登录、展示错误提示等类别范围含义2xx成功请求被正常处理4xx客户端请求有问题参数、权限等5xx服务端服务器内部出错通常不是用户能修的常见的状态码状态码含义典型场景本项目200OKGET 列表/单条、PUT 更新、登录成功Express 默认可省略.status(200)201CreatedPOST /api/auth/register、POST /api/todos创建成功204No ContentDELETE /api/todos/:id删除成功无响应体400Bad Request缺少/格式错误的 body、密码太短、id不是数字401Unauthorized未带 token、token 无效/过期、邮箱或密码错误403Forbidden已登录但 Todo 不属于当前userId越权404Not FoundTodo 不存在或未匹配到任何路由兜底中间件409Conflict注册时邮箱已被占用PrismaP2002唯一约束冲突500Internal Error未捕获异常next(error)进入全局错误处理中间件用法原则1. 成功时按「有没有新建/删除资源」选 200 / 201 / 204GET /api/todos → 200 JSON 数组 POST /api/todos → 201 新建的 todo PUT /api/todos/:id → 200 更新后的 todo DELETE /api/todos/:id → 204body 为空res.status(204).send() POST /api/auth/login → 200 { token, user }登录不算「创建资源」用 200 即可2. 失败时先区分「谁的错」请求格式/参数不对 → 400客户端改请求 身份未证明/证明无效 → 401客户端去登录或换 token 身份有效但无权操作 → 403客户端别碰这条资源 资源本身不存在 → 404客户端换 id 或放弃 资源冲突重复注册 → 409 服务端代码/DB 异常 → 500客户端可提示「稍后重试」3. 401 vs 403面试常问状态码问的是本项目中何时返回401「你是谁」无 Bearer、token 过期、邮箱密码错误403「你不能动这个」Todo 存在但不属于当前 userId4. 404 与 403 的判断顺序重要单条资源接口GET/PUT/DELETE/:id必须先判404不存在再判403越权if(!existing)returnres.status(404).json({error:Todo not found});if(existing.userId!req.userId)returnres.status(403).json({error:Forbidden});顺序反了先写existing?.userId ! req.userId会把「不存在的 id」误报成 403还会泄露「这条 id 是否存在」的信息。5. 响应体约定4xx / 5xx统一{ error: 可读说明 }便于前端展示401 登录失败故意用Invalid email or password不区分「邮箱不存在」和「密码错」避免枚举用户204不要带 JSON body500生产环境勿把堆栈返回给客户端6. Express 里怎么写returnres.status(400).json({error:Title is required});// 早返回带状态码res.status(201).json(newTodo);// 创建成功res.json(todos);// 省略 status默认 200res.status(204).send();// 无内容next(error);// 交给全局中间件 → 500踩过的坑migration 加 NOT NULL 字段表里有旧 User 数据时会失败 → 先清关联表数据或分步 migrate404 / 403 顺序existing?.userId ! req.userId写在!existing前面 → 不存在也返回 403DELETE 漏所有权校验只查存在就删 → 任意登录用户可删别人的 Todomiddleware catchjwt.verify失败应返回 401不要next(error)进 500响应泄露 passwordprisma.user.create用select排除 password登录返回user对象不含 password