【应用】API接口安全性设计1

1. 接口的安全性设计:

主要围绕Token、Timestamp和Sign三个机制展开设计,保证接口的数据不会被篡改和重复调用,下面具体来看:
a) Token授权机制
用户使用用户名密码登录后服务器给客户端返回一个Token(通常是UUID),并将Token-UserId 以键值对的形式存放在缓存服务器中(如:redis中)。服务端接收到请求后进行Token验证,如果Token不存在,说明请求无效。Token是客户端访问服务端的凭证。
b) 时间戳超时机制:
用户每次请求都带上当前时间的时间戳timestamp,服务端接收到timestamp后跟当前时间进行比对,如果时间差大于一定时间(比如10分钟),则认为该请求失效。时间戳超时机制是防御DOS攻击的有效手段。
c) 签名机制:
将[Token]和[时间戳]加上其他请求参数再用MD5或SHA-1算法加密,加密后的数据就是本次请求的签名sign,服务端接收到请求后以同样的算法得到签名,并跟当前的签名进行比对,如果不一样,说明参数被更改过,直接返回错误标识。签名机制保证了数据不会被篡改。
d) 拒绝重复调用(非必须)
客户端第一次访问时,将签名sign存放到缓存服务器中,超时时间设定为跟时间戳的超时时间一致,二者时间一致可以保证无论在timestamp限定时间内还是外 URL都只能访问一次。如果有人使用同一个URL再次访问,如果发现缓存服务器中已经存在了本次签名,则拒绝服务。如果在缓存中的签名失效的情况下,有人使用同一个URL再次访问,则会被时间戳超时机制拦截。这就是为什么要求时间戳的超时时间要设定为跟时间戳的超时时间一致。拒绝重复调用机制确保URL被别人截获了也无法使用(如抓取数据)。

2. 整个流程如下:

a、客户端通过用户名密码登录服务器并获取Token;
b、客户端生成时间戳timestamp,并将timestamp作为其中一个参数;
c、客户端将所有的参数,包括Token和timestamp按照自己的算法进行排序加密得到签名sign;
d、将token、timestamp和sign作为请求时必须携带的参数加在每个请求的URL后边(http://url/request?token=123&timestamp=123&sign=123123123);
e、服务端写一个过滤器对token、timestamp和sign进行验证,只有在sign有效、token有效(在缓存中存在)、timestamp未超时,三种情况同时满足,本次请求才有效。在以上三中机制的保护下,如果有人劫持了请求,并对请求中的参数进行了修改,签名就无法通过;
f、如果在上一步, 我们将签名放在缓存中(如:Redis中)。 用户每次请求的时候,先核对一下签名是否使用过。如果使用过,直接拒绝范围;如果没有用过,才经过上一步的验证。这样就可以防止DOS的攻击。也可以避免用户重复提交带来的其他问题;

3. 具体实现

a. token缓存可以使用redis来实现。Token-UserId 存在redis之中。登录的时候存入,注销的时候删除(注意定时删除不用的Token与UserId);
b. 客户端每次对用户的请求,首先判断签名是否存在,如果存在直接拒绝;如果签名不存在,去验签,通过验签后缓存到redis中(注意定时清理不用的签名。 一般所有的签名设置5分钟制动销毁)。 
c. 签名代码的实现
public static String createSign(Map<String, String> params, boolean encode)    
            throws UnsupportedEncodingException {    
        Set<String> keysSet = params.keySet();    
        Object[] keys = keysSet.toArray();    
        Arrays.sort(keys);    
        StringBuffer temp = new StringBuffer();    
        boolean first = true;    
        for (Object key : keys) {    
            if (first) {    
                first = false;    
            } else {    
                temp.append("&");    
            }    
            temp.append(key).append("=");    
            Object value = params.get(key);    
            String valueString = "";    
            if (null != value) {    
                valueString = String.valueOf(value);    
            }    
            if (encode) {    
                temp.append(URLEncoder.encode(valueString, "UTF-8"));    
            } else {    
                temp.append(valueString);    
            }    
        }    
    
        return MD5Utils.getMD5(temp.toString()).toUpperCase();    
    }

d. 验签校验

String sign = request.getParameter("sign");
Enumeration<?> pNames =  request.getParameterNames();
Map<String, Object> params = new HashMap<String, Object>();
while (pNames.hasMoreElements()) {
  String pName = (String) pNames.nextElement();
  if("sign".equals(pName))continue;
  Object pValue = request.getParameter(pName);
  params.put(pName, pValue);
}

猜你喜欢

转载自blog.csdn.net/shgh_2004/article/details/80941257
今日推荐