APP接口开发token安全之请求校验规则

bailangriver 2019-06-27

移动应用开发过程中请求服务端采用token(在计算机身份认证中是令牌(临时))方式请求方式进行,get请求方式下token直接暴露在请求路径很容易被别人利用进行篡改进行重复提交等,怎样保证移动端安全成为后台开发者所面临的问题,,因为涉及敏感行业数据APP接口开发过程中安全性成为要求,在网上看了很多资料最后选择采用token+time+nonce+sign方式在过滤器层进行校验,APP进行拼接加密,后端Filter进行解密校验,优点实现简单,能够很好保证请求过程中APP端到服务器端安全性,因此此种校验方式被很多互联网公司所采用。

一、技术实现原理:

token: token为用户登录时获取的临时令牌
time: APP获取到的当前时间,形式为long类型,time时间可进行匹配APP获取时间和服务器时间差如果大于某个时间则失效(如链接超过60秒失效,具体可参考源码),可防止别人截取数据后修改数据内容进行提交
nonce:APP接口通过UUID等形式获取的随机不重复内容,可以讲nonce存放在session或redis中并设置合适的超时时间,然后下次请求时通过nonce取session或redis中是否存在,如果存在则认为重复提交请求,防止被别人篡改后提交数据
sign: 通过token+time+nonce三个参数加密后的密文(sign加密可使用使用ase,md5等加密方式,需要注意md5加密不可逆,实现过程有所区别),sign主要校验token、time、nonce有没有被别人篡改数据

二、技术实现过程:

1在过滤层实现SecurityFilter类

 public class SecurityFilter extends Filter {

private static String secretKey = ApplicationConfig.secretKey;
private static String ivParameter = ApplicationConfig.ivParameter ;
// 本文采用ase加密 将加密secretKey及ivParameter 的保存在properties文件中,使用公共接口加载,用户可以选择其他加密方式

@Override
protected void doDestroy() {
    // TODO Auto-generated method stub

}

@Override
protected void doFilter(HttpServletRequest request,
        HttpServletResponse response, FilterChain chain) throws Throwable {
    String requestUri = request.getRequestURI();
    //登录接口则直接不适用
    if(requestUri.indexOf("applogin")>-1){
          chain.doFilter(request, response);
    } else {
        MsgEntity msg = new MsgEntity();
        // 获取request的URL,用于判断该请求是否超时
        String time = request.getParameter("time");
        // 获取请求nonce,用于判断是否用户重复提交
        String nonce = request.getParameter("nonce");
        // 获取token码,实际传入token值
        String token = request.getParameter("token");
        // 获取sign,通过time,nonce,token合并进行加密后密文,需要注意加密顺序,平台必须与APP一致
        String sign = request.getParameter("sign");
        // 判断数据是否存在
        if (StringUtils.isEmpty(time) || StringUtils.isEmpty(nonce)
                || StringUtils.isEmpty(token) || StringUtils.isEmpty(sign)) {
            msg.setMsg("登录失败");
            msg.setState(false);
            HttpRequestUtils.writeResponseJsonString(response, msg);
            return;
        }
        //在实际开发过程中传入数据发现存在%号无法进行解密,需要进行URLDecoder.decode进行解码
        if (sign.indexOf("%") > -1) {
            sign = URLDecoder.decode(sign, "UTF-8");
        }
        // 判断请求是否超过60秒,超过60秒则次请求链接失效,返回登录失败,可根据网络情况适当延长或者缩短时间
        if (difference(time) > 60) {
            msg.setMsg("登录失败");
            msg.setState(false);
            HttpRequestUtils.writeResponseJsonString(response, msg);
            return;
        }
        // 判断是否是重复请求,如果请求nonce存在则登录失败,不存在则将nonce存在redis中或session中并设置合适超时时间
        if (JedisUtils.exists(nonce)) {
            msg.setState(false);
            msg.setMsg("登录失败");
            HttpRequestUtils.writeResponseJsonString(response, msg);
            return;
        } else {
            JedisUtils.set(nonce, "0", 70);
        }
         
        // aes 解密,sign和token + time + nonce进行比较判断数据数据是否存在篡改
        String mingwei = "";
        try {
            mingwei = AesUtils.decrypt(sign);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        if (!mingwei.equals(token + time + nonce)) {
            msg.setState(false);
            msg.setMsg("登录失败");
            HttpRequestUtils.writeResponseJsonString(response, msg);
            return;
        }
         //如果请求全部验证通过则放行
        chain.doFilter(request, response);
    }

三、使用到的工具方法

//主要用于系统时间-APP发送数据时生成日期得到当前差值(秒)
public int difference(String past) {
    long newTimestamp = new Date().getTime();
    return Math.abs((int) (newTimestamp - Long.parseLong(past)) / 1000);
}
   }
    
        /**
 * aes 解密
 * 
 * @param sSrc
 * @return
 * @throws Exception
 */
public static String decrypt(String sSrc) throws Exception {
    try {
        byte[] raw = secretKey.getBytes("ASCII");
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        Cipher cipher = Cipher.getInstance("AES / CBC / PKCS5Padding");
        IvParameterSpec iv = new IvParameterSpec(ivParameter.getBytes());
        cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
        byte[] encrypted1 = new BASE64Decoder().decodeBuffer(sSrc);// 先用base64解密
        byte[] original = cipher.doFinal(encrypted1);
        String originalString = new String(original, "UTF-8");
        return originalString;
    } catch (Exception ex) {
        return null;
    }
}

以上为过滤实现token校验规则代码,在开发过程中可以参考此代码进行适当修改进行使用

相关推荐