RESTful-接口设计方案

一、Http理解

Http超文本传输协议。

超文本:包括:文字,图片,音频,视频等。

传输:客户端向服务端发东西,服务端向客户端发东西。

协议:三方协议。怎么传,错误处理。责权利。

总结:在计算机世界中:两点(客户端,服务端)直接传输 超文本的一个约定 、 规范。应用层协议。

二、域名解析过程

  1. www.demo.com

  2. 浏览器缓存

  3. 操作系统缓存。(hosts,有可能被篡改)

  4. LDNS(本地域名服务器)

  5. 根域名(cn,com):com

  6. 主域名服务器(demo.com)

  7. 具体域名服务器(www.demo.com)-->ip

  8. 缓存(ttl,过期时间)

三、接口设计

RESTful(Representational State Transfer)资源表现层状态转移。

资源(Resources)

REST的名称"表现层状态转化"中,省略了主语。"表现层"其实指的是"资源"(Resources)的"表现层"。

所谓"资源",就是网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的实在。你可以用一个URI(统一资源定位符)指向它,每种资源对应一个特定的URI。要获取这个资源,访问它的URI就可以,因此URI就成了每一个资源的地址或独一无二的识别符。

所谓"上网",就是与互联网上一系列的"资源"互动,调用它的URI。

表现层(Representation)

"资源"是一种信息实体,它可以有多种外在表现形式。我们把"资源"具体呈现出来的形式,叫做它的"表现层"(Representation)。

比如,文本可以用txt格式表现,也可以用HTML格式、XML格式、JSON格式表现,甚至可以采用二进制格式;图片可以用JPG格式表现,也可以用PNG格式表现。

URI只代表资源的实体,不代表它的形式。严格地说,有些网址最后的".html"后缀名是不必要的,因为这个后缀名表示格式,属于"表现层"范畴,而URI应该只代表"资源"的位置。它的具体表现形式,应该在HTTP请求的头信息中用Accept和Content-Type字段指定,这两个字段才是对"表现层"的描述。

状态转化(State Transfer)

访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,势必涉及到数据和状态的变化。

互联网通信协议HTTP协议,是一个无状态协议。这意味着,所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生"状态转化"(State Transfer)。而这种转化是建立在表现层之上的,所以就是"表现层状态转化"。

客户端用到的手段,只能是HTTP协议。具体来说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源。

1、接口规范

接口URI动词设计,这些动词就是HTTP的操作方式,无需提现在URI中。

get 查询:单条记录,列表。

post: 新建资源。(新增订单)

put: 更新(全量更新)

patch: 更新(局部更新)

delete:删除。

例如:查询用户信息,再加上版本号

GET  /user/v1.0/{userId}

版本号可以在HTTP请求头信息的Accept字段中进行区分

Accept: vnd.example-com.foo+json; version=v1.0

接口范例

应用添加
path:/app
method:post
请求参数:
{
    "enAppName":"英文应用名称",
    "chAppName":"中文应用名称",
    "operator":"操作人",
    "dataSourceType":"1",
    
}
返回参数:
{
    "code":0,
    "message":"消息内容",
    "data":{}
}
​
应用修改
path:/app
method:put
请求参数:
{
    "appId":"uuid",//新增不填,修改必填
    "enAppName":"英文应用名称",
    "chAppName":"中文应用名称",
    "operator":"操作人"
    
}
返回参数:
{
    "code":0,
    "message":"消息内容",
    "data":{}
}
​
应用修改(局部)
path:/app/{appId}
method:patch
请求参数:
{
    "chAppName":"中文应用名称",
}
返回参数:
{
    "code":0,
    "message":"消息内容",
    "data":{}
}
​
应用删除
path:/app/{appId}
method:delete
请求参数:
无
返回参数:
{
    "code":0,
    "message":"消息内容",
    "data":{}
}
​
应用列表
path:/app/list?pageNumber=1&pageSize=10&queryStr=查询条件&sortrule=+a字段,-b字段
method:get
​
返回参数:
{
    "code":0,
    "message":"消息内容",
    "data":{
        "list":[
            {
                "appId":"uuid",
                "enAppName":"英文应用名称",
                "chAppName":"中文应用名称",
                "dataSourceType":"1",
                "operator":"操作人",
                
                "createTime":毫秒长整型,
                "updateTime":毫秒长整型
            },
            {
                "appId":"uuid",
                "enAppName":"英文应用名称",
                "chAppName":"中文应用名称",
                "dataSourceType":"1",
                "operator":"操作人",
                
                "createTime":毫秒长整型,
                "updateTime":毫秒长整型              
            }
        ],
        "pageNumber":1,//第几页,从1开始。
        "pageSize":10,//页面大小    
        "totalRecord":100,//总数
    }
}
​
应用详情
path:/app/{appId}
method:get
​
返回参数:
{
    "code":0,
    "message":"消息内容",
    "data":{
                "appId":"uuid",
                "enAppName":"英文应用名称",
                "chAppName":"中文应用名称",
                "dataSourceType":"1",
                "operator":"操作人",
                "createTime":毫秒长整型,
                "upateTime":毫秒长整型
        }
}

2、接口防篡改

消费端:采用参数签名方式,对请求的入参进行签名,签名值和参数一同传给服务端。

服务端:对请求中的参数进行签名,用签名值和消费端传来的签名值对比一致即可。

签名实现demo

@Slf4j
public class SignUtils {
​
    public static String appSecret = "SP000001";
​
    // 校验 签名
    public static Map checkSign(Map map){
        Map resultMap = new HashMap();
        String sign = (String) map.get("sign");
        map.remove("sign");
        long timestamp = Long.parseLong((String) map.get("timestamp"));
        // 让接口在有效期内访问
        long time = System.currentTimeMillis() - timestamp;
        if (time > 1000 * 60 * 3){
            resultMap.put("code", "0001");
            resultMap.put("msg", "签名过期,请重新生成!");
            return resultMap;
        }
​
        // 生成sign
        String s = generatorSign(map);
        if (s.equals(sign)){
            resultMap.put("code", "0000");
            resultMap.put("msg", "SUCCESS");
            return resultMap;
        }else {
            resultMap.put("code", "0002");
            resultMap.put("msg", "签名错误");
            return resultMap;
        }
    }
​
    // 根据map生成签名
    public static String generatorSign(Map map){
        map.remove("sign");
        // 排序
        Map stringObjectMap = sortMapByKey(map);
        // 转格式
        Set> entries = stringObjectMap.entrySet();
        StringBuilder sb = new StringBuilder();
        for (Map.Entry e : entries){
            sb.append(e.getKey()+","+e.getValue()).append("#");
        }
​
        // 组装secret  在参数的后面 添加 secret
        sb.append("secret").append(appSecret);
        log.info("签名前内容:" + sb.toString());
        // 生成签名
        return MD5Util.md5(sb.toString());
        // sha256生成 签名
//      return Sha256Utils.getSHA256(sb.toString());
    }
​
    public static Map sortMapByKey(Map map){
​
        Map sortMap = new TreeMap<>(new MyMapComparator());
​
        sortMap.putAll(map);
​
        return  sortMap;
​
    }
​
    static class MyMapComparator implements Comparator{
        @Override
        public int compare(String o1, String o2) {
            return o1.compareTo(o2);
        }
    }
​
    public static void main(String[] args) {
        HashMap map = new HashMap<>();
        map.put("appId","1");
        map.put("name","2");
        map.put("urlParam","3");
        map.put("timestamp","1651668355151");
​
        String s = generatorSign(map);
        System.out.println(s);
    }
​
}

3、接口防重复请求

这个接口请求不可篡改后,另一个问题又来了。如果一笔交易被同样的请求多次请求,会导致交易重复出现。于是出现了两种解决方式。

  • 在接口请求中,添加timestamp时间参数。

消费端:传入timestamp为当前时间。

服务端:服务端接收到请求后,会对timestamp做一次校验,超出规定时间(如:2 min)范围的请求,被视为非法请求。

实现已在上面的demo中包含。

  • 持久化请求url

如果安全级别非常高了。可能存在2分钟内,被攻击者重复调用的情况,此时则需要将请求的url缓存到Redis或者持久化到数据库。消费端每次调用此接口都需要先查询此url是否存在与缓存或者数据库中。存在则被定义为非法请求。

实现未写出,后续更新

你可能感兴趣的:(项目方案,restful,java,后端,安全)