接口参数加密

最近,公司有一些接口需要对合作方开放,需要做参数加密以及合作方身份认证,下面是我的做法:

思想:因为部分接口需要加密,所以通过自定义注解,加上注解的接口才进行校验。
我们会为合作方下发 partnerId 和 privateKey ,客户端和服务端保留这两个信息。
加密方式:

项目加密方式
1,必须提交的参数
POST提交
signature: 签名 //必填
partnerId: 合作者ID //必填
timestamp : 调用接口时的时间戳 单位秒 如 1489660879 //必填
业务参数
2,签名生成规则
将参数(业务参数+partnerId+timestamp+privateKey)按照A-Z的顺序排序后按照KEY=VALUE的方式连接成字符串。
如aa=2323operator=ltpageIndex=1pageSize=10partnerId=6079410844privateKey=4EUJ7HEV8UEEFS5A0dxgtimestamp=1565235925
然后将该字符串MD5后截取3-28位后用sha1加密
sha1(substr(md5(aa=2323operator=ltpageIndex=1pageSize=10partnerId=6079410844privateKey=4EUJ7HEV8UEEFS5A0dxgtimestamp=1565235925),3,28))
以上参数
timestamp可以有效防止别人抓包进去模拟请求,或者脚本批量执行已经出现的请求
signature可以防止别人调用自己的api
privateKey作为秘钥,不允许在网络上传递
partnerId作为合作者唯一标识

具体验证代码如下:

package com.haoma.number.encrypt;


import com.google.common.collect.Lists;
import com.haoma.api.number.entity.auth.AuthUserEntity;
import com.haoma.number.service.auth.IAuthUserService;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;

import java.util.Collections;
import java.util.List;
import java.util.Map;


/**
 * @ClassName WebSignature
 * @Description: 调用接口进行验证签名
 * @Author yanbo
 * @Date 2019/8/7 15:19
 **/
public class WebSignature {


    /**
     * 进行验签看用户是否合法、参数是否合法、是否超时
     *
     * @param signature 客户端签名后的值
     * @param appId     即:下发给第三方调用者的唯一表示
     * @param timestamp 时间戳 -秒级
     * @return 合法返回空字符;否则返回错误原因
     */
    @SuppressWarnings("unchecked")
    public static String signature(String signature, String partnerId, IAuthUserService authUserService, String timestamp,
                                   Map<String, String[]> params) {
        String errorMsg = "";
        if (StringUtils.isBlank(signature)) {
            errorMsg = "auth signature blank error";
            return errorMsg;
        }
        if (StringUtils.isBlank(partnerId)) {
            errorMsg = "auth partnerId blank error";
            return errorMsg;
        }
        if (StringUtils.isBlank(timestamp)) {
            errorMsg = "auth timestamp blank error";
            return errorMsg;
        }
        if (timestamp.trim().length() != 10) {
            errorMsg = "auth timestamp length error,unit min ";
            return errorMsg;
        }
        //时间校验,前后最多差4min
        long curSeconds = System.currentTimeMillis() / 1000;
        Long minMillisDiff = (curSeconds -
                NumberUtils.toLong(timestamp, curSeconds)) * 1000;
        if (minMillisDiff > 4 * 60 * 1000 || minMillisDiff < -4 * 60 * 1000) {
            errorMsg = "auth timestamp fail !";
            return errorMsg;
        }
        //校验partnerId合法性
        AuthUserEntity authUserEntity = authUserService.findByPartnerId(partnerId);
        if (authUserEntity == null) {
            errorMsg = "partnerId is invalid!";
            return errorMsg;
        }
        params.remove("signature");
        params.put("privateKey", new String[]{authUserEntity.getPrivateKey()});
        //1:排序
        List<String> keys = Lists.newArrayList(params.keySet());
        Collections.sort(keys, String::compareTo);
        //2:拼接
        String keyValues = "";
        for (int i = 0; i < keys.size(); ++i) {
            String key = keys.get(i);
            String[] val = params.get(key);
            if (val.length > 0) {
                keyValues = keyValues + (key + "=" + StringUtils.join(Lists.newArrayList(val), ","));
            } else {
                keyValues = keyValues + (key + "=");
            }
        }
        //3.加密  按字母顺序排序后格式如下 取3-28位
        //signature = sha1(substr(md5(adf=123123bb=56863passwd=123123privateKey=pikb25cc12ee8e677418c738460b05timestamp=1489659289180uname=yanboparam5=sadfasdf),3,28))
        String serverSignature = DesUtil.SHA1(DesUtil.getMd5(keyValues).substring(3, 28));
        if (!signature.equalsIgnoreCase(serverSignature)) {
            errorMsg = "auth signature fail !";
        }
        return errorMsg;
    }



}

自定义注解类:

package com.haoma.util.annotation;

import java.lang.annotation.*;

/**
 * @ClassName MustAuthToken
 * @Description: 需要authToken验证
 * @Author yanbo
 * @Date 2019/8/6 15:19
 **/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SignatureAuth {


}

拦截器:

package com.haoma.number.interceptor;

import com.alibaba.fastjson.JSON;
import com.haoma.api.util.bo.ResultBo;
import com.haoma.number.encrypt.WebSignature;
import com.haoma.number.service.auth.IAuthUserService;
import com.haoma.util.annotation.SignatureAuth;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;

/**
 * @author yanbo
 * @ClassName: AuthSignInterceptor
 * @Description: 进行验签看用户是否合法、参数是否合法、是否超时
 * @date 2019年8月8日 下午5:08:48
 */
@Component
public class AuthSignInterceptor extends HandlerInterceptorAdapter {

    private static Logger logger = LoggerFactory.getLogger(AuthSignInterceptor.class);

    @Autowired
    private IAuthUserService authUserService;


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        SignatureAuth signatureAuth;
        if (handler instanceof HandlerMethod) {
            signatureAuth = ((HandlerMethod) handler).getMethodAnnotation(SignatureAuth.class);
        } else {
            return true;
        }

        //1:加密校验
        if (signatureAuth != null) {
            logger.trace("=======preHandle=======" + request.getRequestURI());
            String signature = request.getParameter("signature");
            String partnerId = request.getParameter("partnerId");
            String timestamp = request.getParameter("timestamp");
            String requestUrl = request.getRequestURI();
            logger.info("请求地址:" + requestUrl);
            Map<String, String[]> params = request.getParameterMap();
            String errorMsg;
            if (StringUtils.isNotEmpty(errorMsg = WebSignature.signature(signature, partnerId, authUserService, timestamp, params))) {
                ResultBo result = ResultBo.error(errorMsg);
                //要加这个否则服务器返回的不是json,有的浏览器还乱码
                //response.setContentType(ServletUtils.JSON_TYPE);
                response.getWriter().println(JSON.toJSONString(result));
                response.getWriter().flush();
                response.getWriter().close();
                return false;
            }
        }
        // 只有返回true才会继续向下执行,返回false取消当前请求
        return true;
    }
}

拦截器生效:

@Configuration
public class NumberWebConfig implements WebMvcConfigurer {
    private final AuthSignInterceptor authSignInterceptor;

    @Autowired
    public NumberWebConfig( AuthSignInterceptor authSignInterceptor) {
        this.authSignInterceptor = authSignInterceptor;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //参数校验拦截器
        InterceptorRegistration authSign = registry.addInterceptor(authSignInterceptor);
        authSign.addPathPatterns("/**");
    }

}

加密工具方法

package com.haoma.number.encrypt;


import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class DesUtil {

  public static String getMd5(String text) {
      try {
          char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8',
                  '9', 'a', 'b', 'c', 'd', 'e', 'f'};
          MessageDigest md = MessageDigest.getInstance("md5");
          md.update(text.getBytes("UTF-8"));

          byte[] bytes = md.digest();
          int j = bytes.length;
          char[] c = new char[j * 2];
          int k = 0;

          for (int i = 0; i < j; i++) {
              byte b = bytes[i];
              c[k++] = hexDigits[b >>> 4 & 0xf];
              c[k++] = hexDigits[b & 0xf];
          }

          return new String(c);
      } catch (Exception ex) {
          throw new IllegalStateException(ex);
      }
  }

  //SHA1 加密算法
  public static String SHA1(String decript) {
      try {
          MessageDigest digest = MessageDigest
                  .getInstance("SHA-1");
          digest.update(decript.getBytes());
          byte messageDigest[] = digest.digest();
          // Create Hex String
          StringBuffer hexString = new StringBuffer();
          // 字节数组转换为 十六进制 数
          for (int i = 0; i < messageDigest.length; i++) {
              String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
              if (shaHex.length() < 2) {
                  hexString.append(0);
              }
              hexString.append(shaHex);
          }
          return hexString.toString();

      } catch (NoSuchAlgorithmException e) {
          e.printStackTrace();
      }
      return "";
  }


}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值