安全|API接口安全性设计(防篡改和重复调用)

API接口的安全性主要是为了保证数据不会被篡改和重复调用,实现方案主要围绕Token、时间戳和Sign三个机制展开设计。

1. Token授权机制 用户使用用户名密码登录后服务器给客户端返回一个Token(必须要保证唯一,可以结合UUID和本地设备标示),并将Token-UserId以键值对的形式存放在缓存服务器中(我们是使用Redis),并要设置失效时间。服务端接收到请求后进行Token验证,如果Token不存在,说明请求无效。Token是客户端访问服务端的凭证。

# uuid 是手机设备的唯一标示
String token = UUID.randomUUID().toString() + "_" + uuid;

2. 时间戳超时机制 用户每次请求都带上当前时间的时间戳timestamp,服务端接收到timestamp后跟当前时间进行比对,如果时间差大于一定时间(比如30秒),则认为该请求失效。时间戳超时机制是防御重复调用和爬取数据的有效手段。 当然这里需要注意的地方是保证客户端和服务端的“当前时间”是一致的,我们采取的对齐方式是客户端第一次连接服务端时请求一个接口获取服务端的当前时间A1,再和客户端的当前时间B1做一个差异化计算(A1-B1=AB),得出差异值AB,客户端再后面的请求中都是传B1+AB给到服务端。

// timeStamp是客户端从Header传过来的值
Long timeStamp = RequestHeaderContext.getInstance().getTimeStamp();
boolean checkTime = checkTime(timeStamp, 30 * 1000);
if (!checkTime) {
    return responseErrorAPISecurity(response);
}

// checkTime方法
public static boolean checkTime(Long time, Integer variable){
    Long currentTimeMillis = System.currentTimeMillis();
    Long addTime = currentTimeMillis + variable;
    Long subTime = currentTimeMillis - variable;
    if (addTime > time && time > subTime){
        return true;
    }
    return false;
}

3. API签名机制 将“请求的API参数”+“时间戳”+“盐”进行MD5算法加密,加密后的数据就是本次请求的签名signature,服务端接收到请求后以同样的算法得到签名,并跟当前的签名进行比对,如果不一样,说明参数被更改过,直接返回错误标识。签名机制保证了数据不会被篡改。

// 请求的API参数,如果是再body,则MD5;如果是param,则原字符串
StringBuffer urlSign = new StringBuffer();

if ("POST".equals(request.getMethod()) || "PUT".equals(request.getMethod())) {
    String bodyStr = RequestReaderUtil.ReadAsChars(request);
    String bodySign = "";
    if (!StringUtils.isEmpty(bodyStr)){
        bodySign = DigestUtils.md5DigestAsHex((bodyStr).getBytes());
    }
    urlSign = new StringBuffer(bodySign);
} else if ("GET".equals(request.getMethod()) || "DELETE".equals(request.getMethod())) {
    String params = request.getQueryString();
    if (params == null){
        params = "";
    }
    urlSign = new StringBuffer(params);
}
// “请求的API参数”+“时间戳”+“盐”进行MD5算法加密
String sign = DigestUtils.md5DigestAsHex(urlSign.append(timeStamp).append(salt).toString().getBytes());

// signature是客户端从Header传过来的值
if (signature.equals(sign)) {
    return true;
} else {
    return false;
}

4. 注意事项 1、因为用户登录的Token是和设备唯一标示绑定的,所以一个用户有可能会有多个有效的Token,那么当用户在修改登录密码时需要把所有的Token删除,我的做法是在Redis保存了一个value是List(值是该用户所有的有效的Token)的Key,当修改密码时会把该Key下的所有Token干掉。 2、客户端每次请求,在Header里面有timeStamp的值,签名中也是用这个timeStamp组合签名的,要确保这两个值是一致的。因为我们在实际开发中,发现客户端的同事在加密时通过函数获取了当前时间A,在请求时也通过函数获取了当前时间B,有时候这两个当前时间会差几毫秒,导致签名校验失败。

/**
  * 登录后由服务端生成并返回
  */
private String token;

/**
  * 安全校验字段(接口参数+时间戳+加盐:取MD5生成)
  */
private String signature;

 /**
  * 设备唯一标识
  */
private String udid;

/**
  * 时间戳,13位,比如:1532942172000
  */
private Long timeStamp;

5. 安全保障总结 在以上机制下, 如果有人劫持了请求,并对请求中的参数进行了修改,签名就无法通过; 如果有人使用已经劫持的URL进行DOS攻击和爬取数据,那么他也只能最多使用30s; 如果签名算法都泄露了怎么办?可能性很小,因为这里的“盐”值只有我们的CTO知道。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏炸天帮5

win32进程概念之句柄表,以及内核对象.

我们知道.我们使用CreateProcess 的时候会返回一个进程句柄.以及线程句柄. 其实在调用CreateProcess的时候.内核中会新建一个EPROCE...

9410
来自专栏逆向技术

win32进程概念之句柄表,以及内核对象.

我们知道.我们使用CreateProcess 的时候会返回一个进程句柄.以及线程句柄. 其实在调用CreateProcess的时候.内核中会新建一个EPROCE...

10720
来自专栏流柯技术学院

LoadRunner上传及下载文件

         web_submit_data("importStudent.do",

28320
来自专栏跟着阿笨一起玩NET

我的WCF之旅(1):创建一个简单的WCF程序

为了使读者对基于WCF的编程模型有一个直观的映像,我将带领读者一步一步地创建一个完整的WCF应用。本应用功能虽然简单,但它涵盖了一个完整WCF应用的基本结构。对...

7810
来自专栏风中追风

java类的加载过程和类加载器的分析

我们知道,我们写的java代码保存的格式是 .java, java文件被编译后会转换为字节码,字节码可以在任何平台通过java虚拟机来运行,这也是java能够跨...

55580
来自专栏orientlu

初试 Protocol Buffer

如下, 一般情况下,进行消息发送的时候,对于 c/c++, 双方统一使用相同的结构体,发送时直接按内存块丢出去,接收后内存块直接赋值给结构体即可。但是这种对于其...

13930
来自专栏.NET后端开发

如何通过经纬度获取地址信息?

摘要 Google Maps API Web Services,是一个为您的地图应用程序提供地理数据的 Google 服务的 HTTP 接口集合。具体包括:Go...

655110
来自专栏技术小站

Python 多线程与多进程

原文地址:http://www.cnblogs.com/whatisfantasy/p/6440585.html

12820
来自专栏黑泽君的专栏

Java程序的运行原理及JVM的启动是多线程的吗?

A:Java程序的运行原理     Java通过java命令会启动java虚拟机。启动JVM,等于启动了一个应用程序,也就是启动了一个进程。   ...

37520
来自专栏拂晓风起

Unity 添加自定义菜单(插件),添加功能

12640

扫码关注云+社区

领取腾讯云代金券