对外 HTTP 接口安全设计全流程指南
适用于:对外开放的后端 HTTP / REST 接口(偏 Java / Spring Boot,但思路通用)
##记得点赞加收藏哦😁😁😁
1. 明确安全需求(先想清楚要防什么)
对外 HTTP 接口,至少要防这几类问题:
- 身份伪造:假外部系统伪装成你的合作方来调用接口。
- 数据篡改:请求在传输过程中被改了(金额被改、账号被改)。
- 重放攻击:别人抓一包重复发(比如同一笔扣款不停重放)。
- 暴力刷接口:恶意高频调用把系统打挂。
- 数据泄露:敏感数据被窃听、打印在日志里。
安全方案的选择,要结合:
- 是 B2B 系统对接,还是对 C 端开放?
- 调用方数量:几个固定合作方 / 大量开发者。
- 数据敏感度:支付、隐私数据 vs 一般业务数据。
下面给出一套通用的 appId + appSecret + 签名 + 时间戳 + nonce 的方案,适合:
- 外部系统对接(第三方商户、合作方等)。
- 需要防篡改、防重放、防刷。
2. 必须先上 HTTPS
没有 HTTPS,其它安全手段都很脆弱。
2.1 做什么事
- 申请证书(阿里云 / 腾讯云 / Let’s Encrypt 免费证书都可以)。
- 用 Nginx / 网关做 HTTPS 终止:
- 外网访问:
https://api.xxx.com/... - 内网服务:仍然走
http://。
- 外网访问:
- Nginx 里把 HTTP 强制 301 重定向到 HTTPS。
- 应用层可以限制只允许 HTTPS,拒绝明文 HTTP。
2.2 要点
- 证书要定期自动续期(比如用 acme 脚本)。
- 不要在公网暴露未加密的管理后台和调试接口。
3. 设计请求安全协议(签名字段约定)
建议统一规范:
- HTTP 方法:比如
POST - URL:
/api/order/create - Header 要求:
appId:调用方 ID。timestamp:时间戳(毫秒或秒,需统一)。nonce:随机字符串(一次性随机数)。sign:签名。
- Body:JSON 结构的业务数据。
3.1 签名规则示例(通用版)
约定:
- 把所有参与签名的参数放在一个 Map 里(包括业务参数、
timestamp、nonce,不包括sign自身)。 - 按 key 字典序 排序。
- 拼接为:
k1=v1&k2=v2&...的格式。 - 在末尾追加:
&appSecret=xxx。 - 用 HMAC-SHA256 / SHA256 / MD5 计算摘要。
- 把结果转为十六进制大写字符串,作为
sign。
例如:
参数:
appId=demoApp
timestamp=1710000000000
nonce=AbC123
body 中业务字段:amount=100, orderNo=ABC001
排序后拼接:
amount=100&nonce=AbC123&orderNo=ABC001×tamp=1710000000000&appSecret=YOUR_SECRET
sign = UPPERCASE( SHA256( 上面这串 ) )
注意:要在文档里 写死一组示例参数 + appSecret + 最终 sign 值,让调用方可以本地对照。
4. 服务端存储和管理 appId/appSecret
设计一张第三方应用表,例如:
CREATE TABLE third_app (
id BIGINT PRIMARY KEY,
app_id VARCHAR(64) UNIQUE NOT NULL,
app_secret VARCHAR(128) NOT NULL,
status TINYINT NOT NULL DEFAULT 1, -- 1=正常 0=禁用
allow_ip VARCHAR(1024) NULL, -- 可选:IP 白名单,逗号分隔
remark VARCHAR(255),
created_at DATETIME,
updated_at DATETIME
);
注意事项:
appSecret不要明文暴露在页面和日志中。- 可以对
appSecret做加密/脱敏存储(比如用 KMS 管理密钥)。 - 提供后台页面管理:新增应用、禁用应用、重置秘钥等。
5. 后端实现统一安全拦截(以 Spring Boot 为例)
5.1 拦截位置
常见选择:
- Spring MVC
HandlerInterceptor。 - 或 Servlet
Filter。 - 网关层(如 Spring Cloud Gateway / Nginx + 自定义模块)。
这里以 HandlerInterceptor 为例。
5.2 校验流程
- 从请求头获取
appId / timestamp / nonce / sign。 - 判空:任何一个缺失直接拒绝。
- 校验时间戳:当前时间 ± 5 分钟运算范围外拒绝(防止长期有效包)。
- 防重放(使用 nonce):
- 组合 key:
api:nonce:{appId}:{nonce}。 - 利用 Redis
SETNX+ 过期时间(如 10 分钟)。 - 如果存在说明
nonce被重复使用,拒绝。
- 组合 key:
- 查 app 信息:
- 根据
appId查询third_app表。 - 检查
status是否可用。 - 可选:检查请求源 IP 是否在
allow_ip白名单中。
- 根据
- 重建参数并验签:
- 取 query 参数 + body 参数(根据约定)。
- 加上
timestamp和nonce。 - 使用
appSecret重新计算sign。 - 与请求头里的
sign对比,不匹配则拒绝。
- 验签通过后:
- 可将
app信息放到requestattribute 或 ThreadLocal,供后续业务层使用。
- 可将
5.3 验签工具类设计要点
关键点:
- 排序一致性:排序规则要清晰且固定(字典序 / ASCII 升序)。
- 空值处理:一般做法是不把值为空的字段放进签名串。
- 编码问题:确保使用统一字符集(如 UTF-8)。
- 统一封装一个
SignUtil,防止多处复制出错。
伪代码结构:
public class SignUtil {
public static String sign(Map<String, String> params, String appSecret) {
// 1. 移除 sign
params.remove("sign");
// 2. 排序
List<String> keys = new ArrayList<>(params.keySet());
Collections.sort(keys);
// 3. 拼接 k=v&...
StringBuilder sb = new StringBuilder();
for (String key : keys) {
String value = params.get(key);
if (value != null && value.length() > 0) {
if (sb.length() > 0) {
sb.append("&");
}
sb.append(key).append("=").append(value);
}
}
// 4. 拼接 appSecret
sb.append("&appSecret=").append(appSecret);
// 5. 计算摘要(例:SHA-256)
return DigestUtils.sha256Hex(sb.toString()).toUpperCase();
}
}
6. 授权:不同 app 能调用哪些接口
认证解决“你是谁”,授权解决“你能干啥”。
6.1 设计权限模型
在 third_app 表增加字段:
scopes或permissions:存放允许访问的接口权限(如 JSON / 逗号分隔)。
定义若干权限:
ORDER_CREATEORDER_QUERYUSER_INFO_QUERY- ……
6.2 服务端使用方式
两种常见做法:
- 自定义注解 + AOP 校验:
- 注解:
@RequiredScope("ORDER_CREATE") - AOP 从当前请求绑定的
app的权限集合中查询是否包含该 scope。
- 注解:
- 和 Spring Security 整合:
- 将
app映射为一个UserDetails,把 scopes 映射成GrantedAuthority。 - 在接口上使用:
@PreAuthorize("hasAuthority('ORDER_CREATE')")。
- 将
无论哪种方式,都要在 验签通过后 把 app 信息放入可访问的上下文中。
7. 限流与防刷
即使是合法 app,也可能因为 bug 或恶意行为导致接口被刷爆。
7.1 限流维度
可以组合:
- appId 维度限流:
- 例如:
每 appId 每秒最多 50 次,超过直接拒绝。
- 例如:
- IP 维度限流:
- 防止单 IP 恶意攻击。
- 接口维度限流:
- 某些重接口(例如扣款)限制更严格。
7.2 实现方式
- 网关 / Nginx:用
limit_req模块限流。 - 应用层:
- 使用
Bucket4j/resilience4j。 - 或自己用 Redis + Lua 实现令牌桶/滑动窗口。
- 使用
7.3 返回提示
限流时统一返回类似:
{
"code": "RATE_LIMITED",
"message": "请求过于频繁,请稍后重试"
}
8. 输入校验与输出脱敏
8.1 输入校验
- DTO 上使用:
@NotNull、@Size、@Pattern等注解。 - 统一异常处理:用
@ControllerAdvice捕获MethodArgumentNotValidException。 - 防注入:
- SQL 建议使用预编译参数(MyBatis、JPA 默认如此)。
- 不要拼接原始用户输入到 SQL 字符串。
8.2 输出脱敏
对于敏感字段:
- 手机号:只显示中间几位,例如:
138****5678。 - 身份证:
1101**********1234。 - 银行卡号:
**** **** **** 1234。
日志里:
- 禁止打印
appSecret、完整的身份证号/卡号等。 sign也尽量不要完整打印,最多输出前几位做排查。
9. 日志与审计
日志是安全的黑匣子,出问题全靠它。
建议记录:
appId。- 请求 URL / HTTP 方法。
- 请求时间、耗时。
- 请求 IP。
- 结果状态(成功 / 失败 + 失败原因)。
- 核心业务字段(适当脱敏)。
可以:
- 将日志收集到 ELK(Elasticsearch + Logstash + Kibana)。
- 方便后续分析:某 app 调用量、某 IP 是否可疑、哪些接口错误率高等。
10. 文档对接:给外部系统看的内容要清晰
对外提供文档时,要避免模糊描述。重点说明:
- HTTP 基本信息:
- 请求方法(GET / POST)。
- 请求 URL。
- Content-Type(一般为
application/json)。
- 请求头字段:
appId:如何获取。timestamp:使用的时间单位(秒 / 毫秒)。nonce:长度、字符集要求。sign:签名算法说明。
- 签名规则:
- 参与签名的字段列表。
- 是否包含 body 参数。
- 排序规则。
- 空值处理方式。
- 字符编码。
- 完整示例:
- 给出一套参数 + appSecret。
- 详细写出:
- 排序后的拼接字符串。
- 中间结果(可选)。
- 最终 sign 值。
最好同时提供:
- 至少一个示例代码(Java / Node / PHP 任选)。
- Postman / curl 调用示例。
11. 总结:一套实用的对外接口安全 Checklist
可以按这个清单自查:
- API 强制走 HTTPS。
- 有 appId/appSecret 机制,且秘钥安全存储。
- 请求带
timestamp + nonce + sign,支持防篡改、防重放。 - 有统一拦截器/过滤器进行验签。
- 不同 app 有权限隔离(按 scope / 权限标识控制可调用的接口)。
- 核心接口(尤其是扣款类)有限流、防刷机制。
- 所有输入参数做了校验,SQL 不拼接裸字符串。
- 响应与日志对敏感数据进行了脱敏处理。
- 有统一的错误码规范和错误返回结构。
- 对外文档写清楚签名规则 + 示例 + 错误码。
把这套做下来,你的对外 HTTP 接口安全性已经能覆盖 90% 常见对接场景。
12. 后续可以扩展的方向
如果后面要做开放平台,还可以继续演进:
- 引入 OAuth2.0 / JWT,支持用户授权、第三方代表用户操作。
- 使用 API 网关统一做:路由、限流、灰度、监控、熔断。
- 针对高风险接口引入二次验证流程(短信 / 人工审核等)。
建议:先把这份文档作为你们“对外接口安全规范”的 v1 版本,以后不断补充演进。

被折叠的 条评论
为什么被折叠?



