API 设计与鉴权:REST / GraphQL / tRPC 与 JWT / OAuth
掌握 API 设计的核心原则和主流风格(REST/GraphQL/tRPC),学会设计清晰、一致、可维护的 API,掌握鉴权方案(JWT/Session/OAuth)的选型和实现,避免常见的 API 设计和安全陷阱
- API的核心价值,就是前后端分离的契约。明确前端能做什么、需要传什么、会得到什么。
- 就按这几个点来做 RESTful API 设计:资源命名、HTTP 方法、状态码、版本化、分页、错误处理
- 直接说 GraphQL 和 tRPC 的核心特性、优缺点、适用场景,以及什么时候不用 REST
- Session(有状态)vs JWT(无状态)vs OAuth2(第三方登录),这三个鉴权方案怎么选
- API 安全的核心措施:HTTPS、输入验证、限流、权限控制、敏感信息保护、CORS
- 学会用 AI 辅助设计 API 和生成接口文档,避开常见的 API 设计反模式
API 是什么:前后端之间的契约
API(Application Programming Interface,应用编程接口)是前后端之间的「契约」——它明确规定了:前端能做什么操作(获取用户信息、创建订单、提交答案);需要传什么参数(用户 ID、订单内容、答案数据);会得到什么响应(用户数据、订单 ID、对错结果);出错时返回什么(错误码、错误信息)。好的 API 设计让前后端可以并行开发、独立测试、顺畅协作;差的 API 设计让前后端每天吵架、改来改去、Bug 不断。
# API 交互示例:用户登录
# 前端请求:
# POST /api/auth/login
# Content-Type: application/json
# { "email": "user@example.com", "password": "123456" }
#
# 后端响应(成功):
# HTTP 200 OK
# { "token": "eyJhbGciOiJIUzI1NiIs...", "user": { "id": 1, "name": "张三" } }
#
# 后端响应(失败):
# HTTP 401 Unauthorized
# { "error": "INVALID_CREDENTIALS", "message": "邮箱或密码错误" }
print("API=前端发请求→后端处理→返回响应,契约明确则协作顺畅")RESTful API 设计:最主流的 API 风格
REST(Representational State Transfer,表述性状态转移)是目前最主流的 API 设计风格,核心思想是「一切皆资源」——把 API 暴露的内容看作资源(用户、订单、课程),用 HTTP 方法表示对资源的操作,用 URL 表示资源的位置。
核心原则
RESTful API 设计原则详解
①资源命名:用名词,不用动词
URL 应该是资源的名称(名词),操作由 HTTP 方法表达。正确:GET /users(获取用户列表)、POST /users(创建用户)、GET /users/123(获取 ID=123 的用户)、PUT /users/123(更新用户)、DELETE /users/123(删除用户)。错误:GET /getUsers、POST /createUser、GET /deleteUser?id=123——把动词放在 URL 里是反模式。资源用复数名词:/users、/orders、/courses,不用 /user、/order。
②HTTP 方法:语义明确
GET、POST、PUT、PATCH、DELETE 这几个 HTTP 方法的分工要记死:GET:查询,幂等、安全POST:创建,非幂等PUT:全量更新,幂等PATCH:部分更新,非幂等DELETE:删除,幂等 幂等的意思是,多次调用结果和调用一次完全一样。所以 GET、PUT、DELETE 是幂等的,POST、PATCH 不是。 别乱用法:不要用 GET 做删除,会被爬虫、预加载误触发;不要用 POST 做查询,没法缓存。
③HTTP 状态码:用对,不要全返回 200
2xx 成功:200 OK(查询/更新成功)、201 Created(创建成功)、204 No Content(删除成功,无响应体)。3xx 重定向:301/302/304(缓存)。4xx 客户端错误:400 Bad Request(参数错误)、401 Unauthorized(未登录/Token 无效)、403 Forbidden(已登录但无权限)、404 Not Found(资源不存在)、409 Conflict(冲突,如重复注册)、422 Unprocessable Entity(参数格式对但业务验证失败)、429 Too Many Requests(限流)。5xx 服务端错误:500 Internal Server Error(服务器内部错误)、502 Bad Gateway、503 Service Unavailable。注意:最常见的错误是所有响应都返回 200,用 body 里的 code 表示错误,这会让 HTTP 缓存、监控、错误处理全部失效,必须用正确的状态码。
④URL 层级:资源关系清晰
嵌套资源用层级表示:GET /users/123/orders(获取用户 123 的所有订单)、POST /users/123/orders(给用户 123 创建订单)、GET /courses/456/lessons(获取课程 456 的所有课时)。层级不要超过 3 层(/a/:a/b/:b/c/:c),太深的层级说明资源关系复杂,应该简化或用查询参数。
⑤查询参数:过滤、排序、分页
过滤:GET /users?role=admin&status=active;排序:GET /users?sort=-created_at(负号表示降序);分页:GET /users?page=1&page_size=20(或游标分页 cursor=xxx,适合大数据量);字段选择:GET /users?fields=id,name,email(不需要的字段不返回,减少传输)。查询参数用于可选的过滤和控制,必填的资源标识用 URL 路径。
⑥版本化:API 演进不破坏老客户端
三种方式:URL 版本(/api/v1/users,最直观最常用)、Header 版本(Accept: application/vnd.myapp.v1+json)、查询参数版本(/api/users?version=1)。就选 URL 版本(/api/v1/、/api/v2/),简单清晰。版本化的目的是:老版本的 API 保持不变(老 App 还能用),新版本的 API 可以做不兼容的改动。别在没有版本化的情况下做不兼容改动(如改字段名、删字段),会导致老 App 崩溃。
⑦错误响应:统一格式,信息充分
统一错误响应格式:{ "error": "INVALID_EMAIL", "message": "邮箱格式不正确", "details": [{ "field": "email", "issue": "format" }] }。error 是机器可读的错误码(英文大写蛇形),message 是给人看的说明,details 是详细的字段级错误(可选)。别只返回「错误」两个字——前端和用户都不知道哪里错了、怎么改。
// RESTful API 设计示例:用户资源的完整 CRUD
// GET /api/v1/users → 获取用户列表(支持 ?page=&page_size=&role=)
// POST /api/v1/users → 创建用户(201 Created)
// GET /api/v1/users/:id → 获取单个用户
// PUT /api/v1/users/:id → 全量更新用户
// PATCH /api/v1/users/:id → 部分更新用户
// DELETE /api/v1/users/:id → 删除用户(204 No Content)
// GET /api/v1/users/:id/orders → 获取用户的订单列表
// POST /api/v1/users/:id/orders → 给用户创建订单
//
// 响应示例(GET /api/v1/users/123):
// {
// "id": 123,
// "email": "user@example.com",
// "name": "张三",
// "role": "learner",
// "created_at": "2026-01-15T08:30:00Z"
// }
//
// 错误响应示例(404):
// { "error": "USER_NOT_FOUND", "message": "用户不存在" }GraphQL:按需取数的 API 风格
GraphQL 由 Facebook 2015 年开源,核心是前端按需取数。不像 REST 那样每个端点返回固定结构的数据,前端写一个查询(Query)描述要什么字段、什么关联数据,后端返回 exactly 要的结构。适合数据关系复杂、前端需求多样、要减少请求次数的场景,比如移动端、复杂仪表盘。
按需取数:前端要什么字段就返回什么,不会过度获取(over-fetching)或不足获取(under-fetching);一个端点搞定所有查询:不需要维护几十个 REST 端点;减少请求次数:一次查询可以获取多个资源和关联数据(如用户+他的订单+订单的商品),REST 可能需要 3-5 次请求;强类型 Schema:API 结构有明确的类型定义,自带文档,前端可以用代码生成工具(GraphQL Code Generator)自动生成 TypeScript 类型;演进灵活:加字段不破坏老查询,不需要版本化。
学习曲线陡:Schema 设计、Resolver、N+1 查询问题、缓存策略都比 REST 复杂;性能优化难:N+1 问题(一个查询触发 N 次数据库查询)需要 DataLoader 等工具解决;复杂查询可能拖垮数据库,需要做查询复杂度限制和深度限制;缓存不如 REST 简单:REST 可以按 URL 缓存,GraphQL 是 POST 一个端点,缓存需要专门处理(Apollo Client 有内置缓存但配置复杂);文件上传/下载不如 REST 直观;不适合简单 CRUD 应用:如果你的 API 就是简单的增删改查,GraphQL 是过度工程,REST 更简单直接。适用:数据关系复杂的大型应用(如 Facebook/GitHub/Shopify)、前端需求多样的平台型产品。
tRPC:端到端类型安全的 API
tRPC(TypeScript Remote Procedure Call)是API风格,核心是前后端都用TypeScript,共享类型定义,调用后端API就像调用本地函数,有完整类型提示和编译期检查。适合全栈TypeScript项目(Next.js、Nuxt)、小团队、追求开发效率。
端到端类型安全:后端改了 API 类型,前端立刻报错,不会出现运行时才发现字段不匹配;调用像本地函数:const user = await api.user.byId.query(123),不需要写 fetch、不需要拼 URL、不需要解析 JSON;自动生成文档和类型;和 Next.js 深度集成(App Router、Server Actions);轻量,不需要 Schema 定义语言(不像 GraphQL 需要写 SDL)。适用:全栈 TypeScript 项目、Next.js 全栈应用、小团队快速开发。本课程的朋友好学 App 如果做全栈 TS,可以考虑 tRPC,但目前是前端静态导出+Node 后端 HTTP API,用 REST 更简单。
只能用于 TypeScript 全栈:前端是 Swift/Android 原生,或后端不是 TS,就用不了;不适合公开 API:tRPC 是为自己的前后端设计的,不适合给第三方开发者用的公开 API,公开 API 用 REST/GraphQL;生态不如 REST/GraphQL 成熟;和特定框架(Next.js)绑定较深。
API 风格选型总结
90% 的应用用 REST 就够了。数据关系特别复杂、前端需要灵活取数的大型应用 → GraphQL。全栈 TypeScript、Next.js、小团队 → tRPC,开发体验最好。微服务间高性能通信 → gRPC(Protobuf 二进制,比 JSON 快 5-10 倍)。实时通信 → WebSocket(双向)或 SSE(Server-Sent Events,单向服务器推送,AI 流式输出用 SSE 最合适,本课程的朋友好学 App AI 对话就是 SSE 流式)。别为了「酷」用 GraphQL/tRPC——如果团队不熟悉、应用不复杂,REST 是最稳妥的选择。
鉴权方案:谁在调用、有没有权限
鉴权(Authentication + Authorization)解决两个问题:Authentication(认证):这个用户是谁?(验证身份);Authorization(授权):这个用户能做什么?(验证权限)。主流方案有三种:Session(有状态)、JWT(无状态)、OAuth2(第三方登录)。
Session(有状态):传统方案
用户登录 → 后端验证账号密码 → 后端创建 Session(存在服务器内存/Redis/数据库),生成一个 Session ID → 后端把 Session ID 存在 Cookie 里返回给前端 → 前端后续请求自动带上 Cookie → 后端根据 Session ID 查到 Session,知道用户是谁。优点:安全(Session 数据存在服务器,前端只有 ID)、可以随时撤销(删 Session 即可)、适合 Web 应用。缺点:有状态(服务器要存 Session,分布式部署需要共享 Session 存储如 Redis)、不适合移动端/App(Cookie 管理不如 Web 方便)、跨域麻烦。
JWT(无状态):现代 API 首选
JWT(JSON Web Token)流程:用户登录 → 后端验证账号密码 → 后端用密钥签名生成一个 JWT(包含用户 ID、过期时间等信息,是一个加密签名的字符串)→ 前端把 JWT 存在 localStorage/内存里 → 前端后续请求在 Header 里带上 Authorization: Bearer <token> → 后端用密钥验证签名,确认 Token 有效且未过期,从中取出用户 ID。优点:无状态(服务器不需要存 Session,分布式部署简单)、适合移动端/App(存在 localStorage,不受 Cookie 限制)、跨域简单。缺点:无法主动撤销(Token 签发后在过期前一直有效,除非维护一个黑名单——那就又变成有状态了)、Token 体积比 Session ID 大、存在 localStorage 有 XSS 风险(被 XSS 攻击偷走 Token)。
// JWT 结构:header.payload.signature(三部分用 . 分隔)
// header: { "alg": "HS256", "typ": "JWT" }
// payload: { "userId": 123, "role": "learner", "exp": 1735689600 }
// signature: HMACSHA256(base64(header) + "." + base64(payload), secret)
//
// 完整 Token 示例:
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEyMywicm9sZSI6ImxlYXJuZXIiLCJleHAiOjE3MzU2ODk2MDB9.abc123xyz
//
// 后端验证:用同样的密钥重新计算 signature,和 Token 里的 signature 对比
// 一致 → 有效;不一致 → 被篡改过,拒绝
// 注意:payload 是 base64 编码,不是加密!任何人都能解码看到 payload 内容
// 所以 JWT 里不能存敏感信息(密码、身份证号)OAuth2 / OpenID Connect:第三方登录
OAuth2 是第三方授权登录的标准协议——用户用微信/Google/GitHub/Apple 账号登你的应用,不用在你这注册账号密码。流程(授权码模式):用户点「用 Google 登录」→ 跳 Google 授权页 → 用户同意授权 → Google 回调你后端带授权码 → 你后端用授权码向 Google 换 Access Token → 用 Access Token 拿用户信息(邮箱、头像)→ 你后端建或更新用户账号,发自己的 JWT。OpenID Connect(OIDC)是在 OAuth2 之上加了身份认证层,专门用于登录场景。适合:不想让用户记密码、提高注册转化率、需要拿第三方平台数据(如微信好友、GitHub 仓库)。
现代前后端分离 + 移动端/App → JWT,无状态、分布式友好、跨域简单;传统服务端渲染 Web 应用 → Session+Cookie,更安全、可随时撤销;第三方登录 → OAuth2/OIDC(微信/Google/Apple),提高注册转化率;服务间调用 → API Key 或 mTLS;JWT 的安全要点:a. 用 HS256 或 RS256 签名,密钥足够长(32+ 字符)且存在环境变量里,不能写代码里;b. 设置合理的过期时间(Access Token 1-2小时,Refresh Token 7-30天);c. 不要在 JWT 里存敏感信息(payload 只是 base64 不是加密);d. 前端存在内存或 httpOnly Cookie(不要存在 localStorage 以防 XSS,虽然 SPA 常用 localStorage);e. 实现 Refresh Token 机制(Access Token 过期后用 Refresh Token 换新的,不需要用户重新登录);f. 需要主动撤销时维护 Token 黑名单(Redis)。本课程的朋友好学 App 目前是纯本地应用,不需要登录和鉴权——等做云端同步时再引入 JWT。
API 安全:必须做的十件事
SQL注入:字符串拼接SQL,被恶意输入篡改查询(' OR '1'='1),用参数化查询;XSS(跨站脚本):用户输入的HTML/JS被直接渲染,偷走Cookie/Token,输出转义+CSP;CSRF(跨站请求伪造):利用用户已登录的Cookie发起恶意请求,用SameSite Cookie或CSRF Token防护;未授权访问:接口没检查权限,用户改ID就能看别人的数据(IDOR),每个接口做权限检查;敏感信息泄露:错误响应返回堆栈跟踪、API Key存在前端、密码明文存储;暴力破解:登录接口不限流,被密码字典爆破;不安全的直接对象引用(IDOR):GET /api/users/123,用户把123改成124就能看别人的信息,检查资源归属。
本节小结
API 是前后端的契约。REST 是最主流的风格:资源命名用名词、HTTP 方法表操作、正确的状态码(不要全 200)、URL 层级清晰、查询参数过滤排序分页、URL 版本化(/api/v1/)、统一错误格式。GraphQL 适合数据关系复杂的大型应用(按需取数、一个端点),但学习曲线陡、N+1 性能问题。tRPC 适合全栈 TypeScript/Next.js 小团队(端到端类型安全、调用像本地函数)。gRPC 适合微服务间高性能通信。实时通信用 WebSocket(双向)或 SSE(单向服务器推送,AI 流式输出首选)。鉴权:现代前后端分离+移动端用 JWT(无状态、分布式友好),传统 Web 用 Session+Cookie,第三方登录用 OAuth2/OIDC。JWT 安全要点:强密钥、合理过期、不存敏感信息、Refresh Token、必要时黑名单。API 安全十条:HTTPS、输入验证、参数化查询防注入、XSS 防护、限流、权限控制、密码哈希、CORS 白名单、错误信息不泄露、依赖更新。下一节讲用 AI 写后端——API 设计好了,怎么用 AI 高效实现。
资深工程师加餐
底层原理 · 大厂视角 · 工程经验,点卡片展开
纯网页包壳容易被应用商店以“最小功能不足”驳回,关键在于用原生能力补足 Web 做不到的部分:权限申请(麦克风/摄像头)、安全键盘与键盘高度、状态栏与安全区、文件与分享、版本更新、扫码等。让 Web 负责快速迭代的内容与 UI,原生负责系统能力与体验兜底,这种混合架构才是套壳 App 的合规且高体验形态。