• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

设计安全的RESTful API协议

武飞扬头像
myshare2022
帮助1

RESTful是目前最流行的接口设计规范,在很多公司有着广泛的应用,特别是我们很多接口还是按调用次数进行收费的,那么我们如何设计一个安全的接口协议呢,总体做到下面两点即可:
1、定义协议标准规范;
      统一入参和响应数据的结构体,有利于调用端统一转化处理;
2、加入安全参数或者数据加密规则;
      如果数据本身没有机密性,则可以只对请求的合法性做安全按设计,那就只加入鉴权校验即可,数据则可以不用做加密处理;
      如果数据是需要高度安全的,那我们可以对data在进行加密处理,客户端通过secretkey解密即可;

下面是协议规范的样例:

接口全局参数及结构说明:

接口格式:

http://***/apis/{function}

协议类型:

http/https

协议头:

Method:post/get

Content-Encoding:utf-8

Content-Type:application/json

请求参数:

请求参数的内容将放到http form、或者url参数中提交

全局入参:

参数名

appid

int

应用ID(必须,由接口提供方分配,可以用来区分不同的授权方,每个appid对应的secretkey也应不同)

timestamp

string

时间戳(ms)(必须,参与签名,保障每次请求的sig不同,服务端可判断时间的有效性,同时可防止重复请求)

sig

string

签名(必须)Md5("appid#timestamp#secretkey")

其中appid、secretkey为接口提供方分配,secretkey为私钥,不可在协议中明文传输

响应结构:

{

"status" : 0,

"success" : true,

"msg" : "OK",

"data" : object

}

字段

status

int

状态码(0=成功,其他表示失败,当然这里能定义出详细的错误码最好;)

success

boolean

是否成功(status=0,此值为true,否则为false)

msg

string

对应状态码的详细描述

data

object

详细数据,不同接口返回数据不同,可以是JSON,也可以是加密的字符串数据;

根据数据安全要求,可对此数据进行AES加密处理,secretkey可作为加密秘钥,客户端通过secretkey解密即可。

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分钟我们可以则可以返回错误;

 下面是服务端示例代码片段如下:

  1.  
    public JSONResponse<List<BrandInfo>> getBrandByIds(
  2.  
    HttpServletRequest request,
  3.  
    @RequestParam(value = "ids", defaultValue = "") String ids) {
  4.  
    try {
  5.  
    JSONResponse checkResult = CheckSig(request);
  6.  
    if (!checkResult.isSuccess()) {
  7.  
    return checkResult;
  8.  
    }
  9.  
    if (StringUtils.isBlank(ids)) {
  10.  
    return this.error("参数ids不能为空!");
  11.  
    }
  12.  
    List<Long> brandIds=UtilsHelper.SplitToLongList(ids,",");
  13.  
    if (brandIds.size()==0) {
  14.  
    return this.error("参数ids格式错误!");
  15.  
    }
  16.  
    List<BrandInfo> list = this.passengerCarBrandService.findByIds(brandIds);
  17.  
    return this.success(list);
  18.  
    } catch (Exception ex) {
  19.  
    ex.printStackTrace();
  20.  
    return this.error(ex.getMessage());
  21.  
    }
  22.  
    }
  23.  
     
  24.  
    /**
  25.  
    * 检查请求合法性
  26.  
    *
  27.  
    * @param request
  28.  
    * @return
  29.  
    */
  30.  
    public JSONResponse CheckSig(HttpServletRequest request) {
  31.  
     
  32.  
    long appid = 0;
  33.  
    long timestamp = 0;
  34.  
    String sig = null;
  35.  
    try {
  36.  
    if (StringUtils.isBlank(request.getParameter("appid"))) {
  37.  
    return JSONResponse.Create(false, "缺少参数:appid");
  38.  
    }
  39.  
    appid = Long.parseLong(request.getParameter("appid"));
  40.  
    } catch (Exception ex) {
  41.  
    return JSONResponse.Create(false, "错误的参数:appid");
  42.  
    }
  43.  
    sig = request.getParameter("sig");
  44.  
    if (StringUtils.isBlank(sig)) {
  45.  
    return JSONResponse.Create(false, "缺少参数:sig");
  46.  
    }
  47.  
    try {
  48.  
    String timestampStr = request.getParameter("timestamp").trim();
  49.  
    if (StringUtils.isBlank(timestampStr)) {
  50.  
    return JSONResponse.Create(false, "缺少参数:timestamp");
  51.  
    }
  52.  
    timestamp = Long.parseLong(timestampStr);
  53.  
     
  54.  
    // 时间戳转换成时间
  55.  
    Date timestamp_time=null;
  56.  
    if (timestampStr.length() < 13) {
  57.  
    //传入的是秒的时间戳需要加0处理成毫秒时间戳
  58.  
    timestamp_time=new Date(Long.parseLong(StringUtils.rightPad(timestampStr, 13, '0')));
  59.  
    }else{
  60.  
    timestamp_time= new Date(timestamp);
  61.  
    }
  62.  
     
  63.  
    //计算相差的分钟数
  64.  
    long minute = Math.abs(new Date().getTime() - timestamp_time.getTime()) / 1000 / 60;
  65.  
    if (minute > 10) {
  66.  
    return JSONResponse.Create(false, "timestamp已过期(与服务器时间相差不得大于10分钟),服务器时间:" DateUtil.format(new Date(),"yyyy-MM-dd HH:mm:ss"));
  67.  
    }
  68.  
    } catch (Exception ex) {
  69.  
    return JSONResponse.Create(false, "错误的参数:timestamp");
  70.  
    }
  71.  
    //通过appid查找应用
  72.  
    AppInfo appinfo = appService.findById(appid);
  73.  
    if (appinfo == null) {
  74.  
    return JSONResponse.Create(false, "应用不存在!");
  75.  
    }
  76.  
    //判断应用是否在有效期内
  77.  
    if (appinfo.getExpireTime().before(new Date())) {
  78.  
    return JSONResponse.Create(false, "应用已过期!");
  79.  
    }
  80.  
    //如果设置了IP白名单,判断IP是否在白名单中
  81.  
    if (StringUtils.isNotBlank(appinfo.getIpWhiteList())) {
  82.  
    String ip = UtilsHelper.getIpAddress(request);
  83.  
    if (!appinfo.getIpWhiteList().contains(ip)) {
  84.  
    return JSONResponse.Create(false, "未授权的IP:" ip "。");
  85.  
    }
  86.  
    }
  87.  
    //根据组合规则生成签名
  88.  
    String newSig = SecureUtil.md5(appinfo.getId() "#" timestamp "#" appinfo.getSecretKey());
  89.  
    if (!newSig.equals(sig)) {
  90.  
    return JSONResponse.Create(false, "鉴权错误!");
  91.  
    }
  92.  
     
  93.  
    return JSONResponse.Create(true, "OK", appinfo);
  94.  
    }
学新通

注意:secretkey千万不能在协议中传输,不可明文暴露,正因为如此,此协议的安全机制适用于系统于系统之家的加密方案,不适合web系统浏览器到服务端的加密方案(secretkey植入js或页面就等同于暴露了secretkey),针对web端解决链路安全的加密方法在后续文章中来讲解;

至此一个安全的接口协议就定义好了。

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgeiefg
系列文章
更多 icon
同类精品
更多 icon
继续加载