RESTful API 的规范性和接口安全性如何取舍

这篇文档是建立在已经对 RESTful 有了一定基础理解的前提下撰写的,主要面向希望进一步理解 RESTful 设计理念在实际项目中如何落地的开发者或架构师。文中不仅会涵盖 Richardson Maturity Model(REST 成熟度模型)、HATEOAS 等核心概念的延伸理解,还将结合常见业务场景(如用户管理、资源变更、接口设计权衡)提供一系列 RESTful 风格接口的实践示例。

如果你已经熟悉 HTTP 动词、资源模型等基本 REST 理念,那么这篇文档将帮助你日后在在规范性、可维护性、安全性等方面进一步提升接口设计的质量与一致性有一些参考和帮助,实现并不是一成不变的,要因地制宜根据实际需求和场景来设计。

什么是RESTFul

REST(Representational State Transfer) 是由 Roy Fielding 博士在 2000 年其博士论文中提出的一种网络架构风格。它定义了一组约束条件和原则,用于构建可扩展、简洁、可维护的 Web 服务。

RESTful 是指符合 REST 架构风格的 Web 接口设计。它并不是 REST 的增强版本,而是对 REST 原则的实际应用和具体实现。通常人们会说"RESTful API"来指代遵循 REST 原则的接口。

为什么大家推崇RESTful范式(规范),它有什么优点。

特点 说明
语义清晰 GET、POST、PUT、DELETE 等语义明确,方便理解和维护
URL 设计优雅 URL 像 /users/1/posts 这样自然,有资源感
易于缓存和调试 GET 请默认被缓存,API 网关或 CDN 支持更好
标准化强 社区支持广,很多工具都支持 RESTful

浏览器、CDN、中间代理服务器(如 Nginx、Varnish)默认会对 GET 请求进行缓存处理,HTTP 协议规定了对 GET 请求的缓存控制字段,如 Cache-ControlETagLast-Modified 等,只要遵循规范的产品,都可以通过这些字段来进行缓存控制。

但也不是说POST就更安全,只不过POST默认不会缓存,果服务端明确在响应中设置了可缓存(如返回 Cache-Control: public, max-age=600),某些中间件或代理可以缓存 POST 响应。对于一些不敏感的操作,依然可以使用GET请求,但请不要定义成get请求,实际上又做了数据库操作等非幂等的操作。 使用GET请求,并通过合适的缓存策略(如 ETag)提升性能。

使用POST请求,合理设置了可缓存(如返回 Cache-Control: public, max-age=600),也是可以使用缓存提升性能,但实际上支持的非常少,需要自己去实现。

比如使用post实现一个聚合查询,则可以在controller和service层,通过redis、es等来实现缓存。

都有谁支持了RESTFul范式

哪些框架和技术支持了RESTFul

  • Java生态

    • Spring MVC/Spring支持注解和约定方式实现RESTful API,搭配Spring Data REST可以快速实现RESTful接口开发
    perl 复制代码
    {
      "id": 123,
      "name": "Alice",
      "email": "alice@example.com",
      "_links": {
        "self": { "href": "/users/123" },
        "update": { "href": "/users/123", "method": "PUT" },
        "delete": { "href": "/users/123", "method": "DELETE" },
        "list": { "href": "/users" }
      }
    }
  • nodejs生态

    • Express
    • Koa
    • fastify
    • Nestjs
  • python生态

    • DJango
    • Flask
  • .NET生态

  • Ruby生态

    • Ruby on Rails
  • Go生态

    • Gin
    • Echo
  • PHP生态

    • Laravel
    • Symfony

RESTful 范式是设计原则和规范,绝大多数主流后端框架都提供了相应的技术支持和约定,方便开发者构建符合 RESTful 的接口。

安全的现实问题:

问题 说明
设备兼容 一些老式网络设备不支持 PUT/DELETE
方法伪造 一攻击者可能伪造请求(如 CSRF),<img src="https://bank.com/api/transfer?to=attacker&amount=10000" />
权限控制颗粒度不足 RESTful 中的资源路径容易泄露数据结构,降低攻击难度
过度暴露数据 RESTful 倾向一次返回资源所有字段,不利于数据最小化原则
GET 参数暴露隐私 查询参数容易被浏览器缓存/历史记录记录
接口状态难统一 真实项目里可能需要事务回滚/逻辑判断,这超出了"资源操作"的边界

常见项目里的"安全 vs RESTful"的冲突举例:

场景 纯 RESTful 写法 更安全的写法
用户删除 DELETE /users/:id POST /users/:id/delete更可控
状态变更 PUT /orders/:id/status POST /orders/:id/confirm清晰含义
多参数查询 GET /products?filter=a&sort=b&range=c POST /products/query避免复杂 query 参数、更安全
批量操作 DELETE /users 带上requestBody POST /users/batch-delete避免了大多数的容器忽略掉delete的RequestBody的问题

RESTFul应用的4个等级

RESTful 的四个等级,也称为 Richardson Maturity Model(理查森成熟度模型),由 Leonard Richardson 提出,用来衡量一个 Web API 遵循 REST 原则的程度。这个模型分成 0 到 3 级共4个级别。

REST 级别 0:单一端点 (The Swamp of POX)

  • API 只有一个端点(通常是 /service/api),所有操作通过 POST 请求发送,使用 XML 或 JSON 包含动作名称和参数。
  • 没有利用 HTTP 的动词,所有操作用同一个 URI。
  • 类似传统的 RPC(远程过程调用)风格。
  • 特点:缺乏资源概念,接口设计粗糙。
bash 复制代码
POST /api
Content-Type: application/json

{
  "action": "createUser",
  "name": "Alice",
  "email": "alice@example.com"
}

所有操作都走一个 /api,通过 action 字段区分动作,资源概念模糊。

并不是级别为0就不可取,很多api、远程调用接口都使用这种方法,早期的WebService系统使用xml格式的数据,更是方便在数据中包含动作名称和参数,且只提供一个接口、端点,一个接口把所有事都干了。随着系统复杂度和技术的提升,Level 0的设计也仅在一些老的系统中存在,并逐渐摒弃。

REST 级别 1:资源导向(资源分层)

  • 资源(如用户、订单等)被映射到不同的 URI,如 /users/orders
  • 通过不同的 URI 来区分不同的资源。
  • 仍可能不使用 HTTP 动词,而是依赖 POST 携带操作参数。
  • 特点:资源层次清晰,但没充分利用 HTTP 动词。
bash 复制代码
POST /users
Content-Type: application/json

{
  "name": "Alice",
  "email": "alice@example.com"
}

满足级别1是基础要求,根据不同对象、业务类型分而治之,而不是通过一个端点、接口来发出请求。

REST 级别 2:HTTP 动词(统一接口)

  • 正确使用 HTTP 动词:

    • GET 用于查询,
    • POST 用于创建,
    • PUT/PATCH 用于更新,
    • DELETE 用于删除。
  • 利用状态码表达请求结果(200, 201, 204, 400, 404 等)。

  • 利用 HTTP 头部(如 Content-Type, Accept)做内容协商。

  • 特点:充分利用 HTTP 协议,资源表现清晰。

bash 复制代码
POST /users
Content-Type: application/json

{
  "name": "Alice",
  "email": "alice@example.com"
}

满足级别2是普遍做法。

REST 级别 3:超媒体驱动(HATEOAS)

  • API 在响应中包含超链接(Hypermedia)指引客户端下一步操作,例如资源中包含"编辑"、"删除"、"禁用"链接。
  • 客户端不需要硬编码 URL 路径,按服务端指示进行状态转移。
  • 是 REST 的最高层次,体现"表述性状态转移"的真正含义。
  • 特点:API 自描述、客户端与服务端松耦合。
json 复制代码
{
  "_links": {
    "self": {
      "href": "http://localhost:8080/persons{&sort,page,size}",
      "templated": true
    },
    "next": {
      "href": "http://localhost:8080/persons?page=1&size=5{&sort}",
      "templated": true
    }
  },
  "_embedded": {
    "persons": [
      {
        "uuid": "123",
        "name": "张三",
        "status": "enabled",
        "_links": {
          "self": {
            "href": "http://localhost:8080/persons/123"
          },
          "disable": {
            "href": "http://localhost:8080/persons/123/disable",
            "type": "POST"
          },
          "delete": {
            "href": "http://localhost:8080/persons/123",
            "type": "DELETE"
          }
        }
      },
      {
        "uuid": "456",
        "name": "李四",
        "status": "disabled",
        "_links": {
          "self": {
            "href": "http://localhost:8080/persons/456"
          },
          "enable": {
            "href": "http://localhost:8080/persons/456/enable",
            "type": "POST"
          },
          "delete": {
            "href": "http://localhost:8080/persons/456",
            "type": "DELETE"
          }
        }
      }
    ]
  },
  "page": {
    "size": 5,
    "totalElements": 50,
    "totalPages": 10,
    "number": 0
  }
}

基于这种数据接口,前端也有开箱即用的基于hal的框架halfredhaltraversontraverson-hal,很遗憾的是这些框架在7-8年就已经不在维护了,并不是说它们落伍了,因为新的替代品也没有产生,而是它已经定型,完全达到了使用要求,不需要再进行迭代了。

Spring Data Rest就是基于HATEOASHAL完全实现了RESTFul 3级别的框架,客户端根据 _links 提供的链接和方法,自动知道接下来可做的操作,接口具备自描述能力,使用前端框架或编写了代码,即使数据再发生变化,代码都不需要更改,因为操作的接口都是从数据中获取的。

虽然最高级别3被认为是最符合RESTFul的接口设计模式,但它也不是"银弹",主要的缺点和挑战在于需要后端精心建模,前端精心编写代码,以适应数据发生变化的情况。

DDD(Domain-Driven Design,领域驱动设计)是RESTful结合的最佳实践之一,往往在使用时,会直接将领域模型直接暴露到响应中。

less 复制代码
@Controller('orders')
export class OrderController {
  constructor(private orderAppService: OrderAppService) {}

  @Get(':id')
  getOrder(@Param('id') id: string) {
    return this.orderAppService.getOrder(id);
  }

  @Post()
  create(@Body() dto: CreateOrderDto) {
    return this.orderAppService.create(dto);
  }
}

解决方法就是增加DTO,只暴露DTO,不暴露Entity,在查询到数据后对数据进行包装、脱敏、处理,而不是直接返回。

DTO

typescript 复制代码
// dto/order-response.dto.ts
export class OrderResponseDto {
  id: string;
  customerName: string;
  totalAmount: number;
  createdAt: string;
}

service

typescript 复制代码
// app.service.ts
@Injectable()
export class OrderAppService {
  constructor(private readonly orderRepository: OrderRepository) {}

  async getOrder(id: string): Promise<OrderResponseDto> {
    const order = await this.orderRepository.findById(id);

    // 手动映射领域模型 -> DTO
    return {
      id: order.id,
      customerName: order.customer.name,
      totalAmount: order.total.amount,
      createdAt: order.createdAt.toISOString(),
    };
  }

  async create(dto: CreateOrderDto): Promise<OrderResponseDto> {
    const order = Order.create(dto); // 创建领域对象
    await this.orderRepository.save(order);

    // 返回 DTO
    return {
      id: order.id,
      customerName: order.customer.name,
      totalAmount: order.total.amount,
      createdAt: order.createdAt.toISOString(),
    };
  }
}

Rest

less 复制代码
// controller.ts
@Controller('orders')
export class OrderController {
  constructor(private readonly orderAppService: OrderAppService) {}

  @Get(':id')
  async getOrder(@Param('id') id: string): Promise<OrderResponseDto> {
    return this.orderAppService.getOrder(id);
  }

  @Post()
  async create(@Body() dto: CreateOrderDto): Promise<OrderResponseDto> {
    return this.orderAppService.create(dto);
  }
}

满足级别3的就很少很少了,基本上只有内部系统,大数据(内部系统)、低代码、IOT才会采用,因为社区支持好,有开箱即用的框架可以使用(Spring Data Rest)大大的节省了后端开发成本,但前端开发成本陡增,对前端要求更高,对业务、数据结构的理解更高。

随着人工智能的发展再回头来看HATEOASHAL,AI可以利用它们提供的数据和方法来实现自动调用和操作API来实现智能决策和任务自动执行。

下面的接口结果可以给AI来实现退货的自动审核,从status获取订单状态,从get_details中获取退货详情根据获取到的信息AI分析判断自动审核,调用approve或reject来完成操作。

json 复制代码
{
  "orderId": "789",
  "status": "pending_return",
  "_links": {
    "self": { "href": "/returns/789" },
    "approve": { "href": "/returns/789/approve", "method": "POST" },
    "reject": { "href": "/returns/789/reject", "method": "POST" },
    "get_details": { "href": "/returns/789/details", "method": "GET" }
  }
}

四个等级的特点

级别 特点 URI 使用 HTTP 动词使用 超媒体(HATEOAS)
0 单一端点,RPC 风格 单一 不使用
1 资源分层,多个 URI 有资源 URI 可能不使用
2 充分利用 HTTP 动词和状态码 资源 URI 正确使用
3 超媒体驱动,接口自描述 资源 URI 正确使用 有,提供导航链接

常用RESTful接口设计

下面是常用的平台级 Web 应用。设计目标是:

  • 遵循资源导向(resource-oriented)
  • 合理使用 HTTP 方法(GET/POST/DELETE/PATCH)
  • 清晰表达语义,避免"动作名"出现在 URL 中(如 /checkUsername

用户相关接口设计(/users 为主资源)

功能 HTTP 方法 接口路径 说明
检查用户名是否存在 GET /users/existence?username=xxx 使用查询参数检查
检查邮箱是否被绑定 GET /users/existence?email=xxx 可复用同一个接口
检查手机号是否被绑定 GET /users/existence?phone=xxx 同上
用户登录 POST /auth/login 提交用户名密码获取 token
用户注销 POST /auth/logout Token 失效或状态变更
用户名或密码错误(错误响应) /auth/login 返回 401 Unauthorized
用户已注销(错误响应) /auth/login 返回 403 Forbidden410 Gone

好友与社交相关(/users/:id/friends 为主资源)

功能 HTTP 方法 接口路径 说明
删除好友 DELETE /users/:id/friends/:friendId 从好友列表移除
取消关注 DELETE /users/:id/following/:targetId 类似 DELETE 操作
添加关注 POST /users/:id/following body 中传 targetId
好友不存在(错误响应) GET/DELETE /users/:id/friends/:friendId 返回 404 Not Found

消息系统接口设计(/messages 为资源)

功能 HTTP 方法 接口路径 说明
回复消息 POST /messages/:messageId/replies 对某条消息添加子消息
删除消息 DELETE /messages/:messageId 删除指定消息

响应结构建议(统一格式)

css 复制代码
{
  "code": 0,
  "message": "OK",
  "data": { ... }
}
错误场景 状态码 message 示例
用户名或密码错误 401 Invalid credentials
用户已注销 403/410 User account is disabled
资源不存在(好友等) 404 Friend not found

这是一般的平台的接口定义,遵循RESTful Level 3(等级3)的等级。可以发现

推荐策略:安全优先,REST 命名语义保留

实战折中建议:

建议 原因
🚫 避免对敏感操作使用 GET GET 会被缓存/预加载,暴露风险
✅ 用 POST /xxx/query 替代复杂的 GET POST 可带复杂体 + 更安全(不被 URL 曝露)
✅ 操作型接口用动词清晰表达意图 /order/:id/confirm/user/:id/reset-password
✅ 加签名/Token/权限校验,不盲信 Method RESTful 本身不等于安全,授权要独立控制
✅ 限速、防重放、防CSRF 特别是修改类接口
✅ 对外公开接口与内部接口分层处理 内部服务可用更"理想"的 RESTful,外部接口应包容性强、可控

特例:金融银行项目 POST-only

然而在金融、银行等行业中,软件开发中只使用POST一种Method,这是出于对安全优先的考虑。

  • GET 请求参数暴露在 URL 中,存在日志泄漏、浏览器缓存、CSRF 等风险
  • 只要涉及用户身份、资金、权限,就禁用 GET 携带敏感参数
  • POST请求的requestBody方便加密解密、get请求queryString加密困难(代码逻辑、存储更复杂)
  • 网关对POST更加友好,老的设备不支持delete、patch等方法

总结

RESTful 是最佳实践的理想形态,接口安全是落地工程必须考虑的现实约束。优先安全,兼顾规范,有能力再提升优雅,并不用最新最高等级的规范实现,一般RESTful达到级别2即可

场景 推荐接口风格
开放平台 API 安全优先,使用 POST+清晰操作名,如 /reset-password
内部系统、大数据系统(B端) 可以 RESTful,前提是权限、验证完备
多筛选、复杂查询、聚合查询 用 POST /resource/query 传 DTO,利于参数校验和扩展
状态变更/操作行为 POST /actionPATCH /resource 更易维护
金融、银行、保险、政府 只用POST(POST-only)

参考文档

docs.spring.io/spring-data...

github.com/spring-atti...

martinfowler.com/articles/ri...

en.wikipedia.org/wiki/Richar...

www.ruanyifeng.com/blog/2011/0...

www.ruanyifeng.com/blog/2018/1...

相关推荐
德育处主任7 分钟前
『OpenCV-Python』配合 Matplotlib 显示图像
后端·python·opencv
hrrrrb16 分钟前
【Spring Boot 快速入门】一、入门
java·spring boot·后端
程序员爱钓鱼21 分钟前
Go语言实战案例-深度优先遍历DFS
后端·google·go
程序员爱钓鱼26 分钟前
Go语言实战案例-广度优先遍历BFS
后端·google·go
( ̄(工) ̄)霸天下28 分钟前
Typescript速通教程
前端·javascript·typescript
超级小忍1 小时前
Spring Boot 配置文件常用配置属性详解(application.properties / application.yml)
java·spring boot·后端
麦兜*1 小时前
基于Spring Boot的审计日志自动化解决方案,结合SpEL表达式和AOP技术,实现操作轨迹自动记录,并满足GDPR合规要求
java·jvm·spring boot·后端·spring·spring cloud·maven
Victor3561 小时前
MySQL(167)如何理解MySQL的Redo Log和Undo Log?
后端
Victor3561 小时前
MySQL(168)MySQL如何实现崩溃恢复?
后端
求知若渴,虚心若愚。4 小时前
Error reading config file (/home/ansible.cfg): ‘ACTION_WARNINGS(default) = True
linux·前端·ansible