设计安全的RESTful API协议
RESTful是目前最流行的接口设计规范,在很多公司有着广泛的应用,特别是我们很多接口还是按调用次数进行收费的,那么我们如何设计一个安全的接口协议呢,总体做到下面两点即可:
1、定义协议标准规范;
统一入参和响应数据的结构体,有利于调用端统一转化处理;
2、加入安全参数或者数据加密规则;
如果数据本身没有机密性,则可以只对请求的合法性做安全按设计,那就只加入鉴权校验即可,数据则可以不用做加密处理;
如果数据是需要高度安全的,那我们可以对data在进行加密处理,客户端通过secretkey解密即可;
下面是协议规范的样例:
接口全局参数及结构说明:
接口格式: |
http://***/apis/{function} |
|||||||||||||||
协议类型: |
http/https |
|||||||||||||||
协议头: |
Method:post/get Content-Encoding:utf-8 Content-Type:application/json |
|||||||||||||||
请求参数: |
请求参数的内容将放到http form、或者url参数中提交 |
|||||||||||||||
全局入参: |
|
|||||||||||||||
响应结构: |
{ "status" : 0, "success" : true, "msg" : "OK", "data" : object }
|
1、根据ID获取汽车品牌
URL:/apis/getBrandByIds
请求参数格式:
{
"ids": "117,509"
}
请求参数字段说明:
参数名称 |
说明 |
是否必须 |
描述 |
ids |
String |
必须 |
品牌ID,多个品牌ID之家用逗号分隔 |
响应数据格式:
{
"success": true,
"msg": "OK",
"status": 0,
"data": [
{
"id": "117",
"name": "AC Schnitzer",
"bfirstletter": "A",
"logo": "/brandimgs/0_117.jpg",
"country": "德国",
"info": "1987年创建的AC Schnitzer是全世界最大的BMW专业改装厂,虽然建厂较晚,但因为是世界最大的BMW的经销商Kohl Automobile Gmbh和Schnitzer赛车集团合作创立。在经验和销售两方面都具有全面优势(早在1964年,Schnitzer就已经开始致力于改装BMW并参加各项赛事)。"
},
{
"id": "509",
"name": "AITO",
"bfirstletter": "A",
"logo": "/brandimgs/0_509.jpg",
"country": "中国",
"info": "2021年12月,小康股份旗下的赛力斯公司在重庆两江智慧工厂发布和华为合作的高端智慧汽车品牌AITO以及赛力斯纯电驱增程平台(DE-i)。AITO旗下首款搭载最新华为鸿蒙HarmonyOS智能座舱车型问界M5已于2021年12月23日正式发布。AITO意为:Adding Intelligence to AUTO.AITO与AUTO一字之差:“I”,即 Intelligence,代表 HarmonyOS 智能座舱等创新技术能力;通过“I”的赋能,AITO 将智能带入汽车,让汽车更智慧。"
}
]
}
响应数据字段说明:
段名称 |
说明 |
描述 |
status |
int |
状态码(0=成功,其他表示失败;) |
msg |
string |
状态码对应的描述 |
data |
[{},{}] |
|
id |
String |
ID(品牌ID,唯一键) |
name |
String |
品牌名称 |
bfirstletter |
String |
首字母 |
logo |
String |
品牌LOGO |
country |
String |
国家 |
info |
String |
品牌介绍 |
根据接口1的格式,往后面定义其他的接口即可,通过上面的接口定义,我们可以看出,在全局入参上加入了appid、timestamp、sig 三个参数,其中sig为appid、timestamp、secretkey组合后通过md5加密得到的签名sig,由于secretkey为线下给到调用方,即便有人知道我们的接口地址,也无法生成出有效的sig签名,这样服务器端通过appid找到对应的secretkey,通过同样的组合方式组合后进行加密得到new_sig,检查new_sig跟接口传入的sig是否一致即可判断请求是否合法了,timestamp我们可以还原成时间,跟系统时间进行比较,比如时间相差超过1分钟我们可以则可以返回错误;
下面是服务端示例代码片段如下:
-
public JSONResponse<List<BrandInfo>> getBrandByIds(
-
HttpServletRequest request,
-
{ String ids)
-
try {
-
JSONResponse checkResult = CheckSig(request);
-
if (!checkResult.isSuccess()) {
-
return checkResult;
-
}
-
if (StringUtils.isBlank(ids)) {
-
return this.error("参数ids不能为空!");
-
}
-
List<Long> brandIds=UtilsHelper.SplitToLongList(ids,",");
-
if (brandIds.size()==0) {
-
return this.error("参数ids格式错误!");
-
}
-
List<BrandInfo> list = this.passengerCarBrandService.findByIds(brandIds);
-
return this.success(list);
-
} catch (Exception ex) {
-
ex.printStackTrace();
-
return this.error(ex.getMessage());
-
}
-
}
-
-
/**
-
* 检查请求合法性
-
*
-
* @param request
-
* @return
-
*/
-
public JSONResponse CheckSig(HttpServletRequest request) {
-
-
long appid = 0;
-
long timestamp = 0;
-
String sig = null;
-
try {
-
if (StringUtils.isBlank(request.getParameter("appid"))) {
-
return JSONResponse.Create(false, "缺少参数:appid");
-
}
-
appid = Long.parseLong(request.getParameter("appid"));
-
} catch (Exception ex) {
-
return JSONResponse.Create(false, "错误的参数:appid");
-
}
-
sig = request.getParameter("sig");
-
if (StringUtils.isBlank(sig)) {
-
return JSONResponse.Create(false, "缺少参数:sig");
-
}
-
try {
-
String timestampStr = request.getParameter("timestamp").trim();
-
if (StringUtils.isBlank(timestampStr)) {
-
return JSONResponse.Create(false, "缺少参数:timestamp");
-
}
-
timestamp = Long.parseLong(timestampStr);
-
-
// 时间戳转换成时间
-
Date timestamp_time=null;
-
if (timestampStr.length() < 13) {
-
//传入的是秒的时间戳需要加0处理成毫秒时间戳
-
timestamp_time=new Date(Long.parseLong(StringUtils.rightPad(timestampStr, 13, '0')));
-
}else{
-
timestamp_time= new Date(timestamp);
-
}
-
-
//计算相差的分钟数
-
long minute = Math.abs(new Date().getTime() - timestamp_time.getTime()) / 1000 / 60;
-
if (minute > 10) {
-
return JSONResponse.Create(false, "timestamp已过期(与服务器时间相差不得大于10分钟),服务器时间:" DateUtil.format(new Date(),"yyyy-MM-dd HH:mm:ss"));
-
}
-
} catch (Exception ex) {
-
return JSONResponse.Create(false, "错误的参数:timestamp");
-
}
-
//通过appid查找应用
-
AppInfo appinfo = appService.findById(appid);
-
if (appinfo == null) {
-
return JSONResponse.Create(false, "应用不存在!");
-
}
-
//判断应用是否在有效期内
-
if (appinfo.getExpireTime().before(new Date())) {
-
return JSONResponse.Create(false, "应用已过期!");
-
}
-
//如果设置了IP白名单,判断IP是否在白名单中
-
if (StringUtils.isNotBlank(appinfo.getIpWhiteList())) {
-
String ip = UtilsHelper.getIpAddress(request);
-
if (!appinfo.getIpWhiteList().contains(ip)) {
-
return JSONResponse.Create(false, "未授权的IP:" ip "。");
-
}
-
}
-
//根据组合规则生成签名
-
String newSig = SecureUtil.md5(appinfo.getId() "#" timestamp "#" appinfo.getSecretKey());
-
if (!newSig.equals(sig)) {
-
return JSONResponse.Create(false, "鉴权错误!");
-
}
-
-
return JSONResponse.Create(true, "OK", appinfo);
-
}
注意:secretkey千万不能在协议中传输,不可明文暴露,正因为如此,此协议的安全机制适用于系统于系统之家的加密方案,不适合web系统浏览器到服务端的加密方案(secretkey植入js或页面就等同于暴露了secretkey),针对web端解决链路安全的加密方法在后续文章中来讲解;
至此一个安全的接口协议就定义好了。
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhgeiefg
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
excel下划线不显示怎么办
PHP中文网 06-23 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
怎样阻止微信小程序自动打开
PHP中文网 06-13 -
TikTok加速器哪个好免费的TK加速器推荐
TK小达人 10-01