Java技术升华分布式&高可用

常用系统间接口调用认证设计

2019-08-02  本文已影响58人  逸如风飞

简介

本文实现接口加密签名及校验。
可适用于绝大多数系统间接口调用。

签名实现

请求签名实现过程

  1. 对请求参数名进行升序排序,排序之后生成请求参数字符串queryStr(拼接时要对参数值进行URLEncoder.encode编码,防止中文等问题),形如:参数a=xx&参数b=22。
  2. 系统间约定加密字符串key ,生成当前时间戳time(毫秒数),获取当前用户编号 uid(注意uid key time 这三个参数均不参与排序)
  3. 将步骤1中生成的queryStr 和 time 、key 、uid 进行拼接,形成待加密字符串str。形如 queryStr &time=xx&key=xxx&uid=xxx
  4. 对待加密字符串str进行MD5加密,并转化成大写,即生成签名字符串hash。
  5. 客户端将uid放到请求header中,将原始请求参数和time、hash一起作参数传递。

校验请求签名过程

  1. 从请求header中获取用户编号uid
  2. 从请求参数里获取签名字符串hash,以及请求时间time
  3. 再将请求参数(除去hash和time)进行一遍签名加密,生成出来正确的签名字符串hash2
  4. 比较hash和hash2即可

代码实现

public class FkSignUtil {
    public static final String UID = "uid";
    /**
     * 加密秘钥
     */
    private static final String KEY = "key可以自定义";
    /**
     * 日志对象
     */
    private static  Logger logger = LoggerFactory.getLogger(FkSignUtil.class);
    /**
     * 生成签名【注意发送请求时一定要带上time 和hash 这2个参数】
     *
     * 功能:将一个Map按照Key字母升序构成一个QueryString. 并且加入时间混淆的hash串
     * @param queryMap  query内容
     * @param time  加密时候,为当前时间;解密时,为从querystring得到的时间;
     * @param uid 表示当前用户id
     * @return
     */
    public static String createSign(Map<String, Object> queryMap,long time, String uid) {
        if(null == uid || "".equals(uid)){
            return null ;
        }
        String qs = sortQueryParamString(queryMap);
        if (qs == null) {
            return null;
        }
        String hash = MD5Util.MD5(String.format("%s&time=%d&key=%s&uid=%s", qs, time , KEY ,uid));
        hash = hash.toUpperCase();
        return hash;
//        String params = String.format("%s&time=%d&hash=%s", qs, time, hash);
//        return params;
    }

    /**
     * 对请求参数进行排序 
     * @param params 请求参数 。注意请求参数中不能包含uid、time【这2参数是排序之后拼接的】
     * @return
     */
    private static String  sortQueryParamString(Map<String,Object> params)  {
        List<String> listKeys = Lists.newArrayList( params.keySet());
        Collections.sort(listKeys);
        StringBuilder content = new StringBuilder();

        for(String param : listKeys){
            try {
                content.append(param).append("=").append(URLEncoder.encode(params.get(param).toString(),"UTF-8")).append("&");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
        if(content.length()> 0){
            return content.substring(0 ,content.length() -1 );
        }
        return  content.toString();
    }

    /**
     * 解密判断是否签名正确
     * @param params 请求参数map【从request的参数中获取】
     * @param uid 表示当前用户id【从header中获取】
     * @return
     */
    public static boolean checkHashSign(Map<String, Object> params,String uid) {
        if(null == uid || "".equals(uid)){
            if (logger.isInfoEnabled()) {
                logger.info("checkHashSign ERROR: uid  is null.");
            }
            return false ;
        }
        if (!params.containsKey("hash") || !params.containsKey("time") ) {
            if (logger.isInfoEnabled()) {
                logger.info("checkHashSign ERROR: hash or  time  is null.");
            }
            return false;
        }
        String hash = (String) params.remove("hash");
        Long time =Long.parseLong((String) params.remove("time"));
        String signHash = createSign(params, time, uid);
        return hash.equals(signHash);
    }

public static void main(String[] args) {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("zhangId", 12321);
        params.put("guanId", true);
        params.put("test", "这是11AVC");
        params.put("name", "是的商家");
        String uid = "asdsa";
        long time = System.currentTimeMillis();
        String hash = createSign(params, time, uid);
        logger.info("hash={} ,time={}", hash, time);
         
        params.put("hash", hash);
        params.put("time", time);
        boolean flag = checkHashSign(params, uid);
        logger.info("校验flag={}  ", flag);
    }

}

优化方案

可以考虑使用token缓存,免加密解密校验。
同时也可以校验time是否不是在有效期内,比如判断time是不是在当前时间的前2分钟之内,防止接口扩散重复调用。

https安全升级

利用阿里云申请 Symantec 免费版 SSL 证书。
在nginx上添加ssl证书到/etc/nginx/ssl.conf文件夹下面。
配置示例:


# 以下属性中以ssl开头的属性代表与证书配置有关,其他属性请根据自己的需要进行配置。
server {
    listen 443;
    server_name localhost;  # localhost修改为您证书绑定的域名。
    ssl on;   #设置为on启用SSL功能。
    root html;
    index index.html index.htm;
    ssl_certificate cert/domain name.pem;   #将domain name.pem替换成您证书的文件名。
    ssl_certificate_key cert/domain name.key;   #将domain name.key替换成您证书的密钥文件名。
    ssl_session_timeout 5m;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;  #使用此加密套件。
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;   #使用该协议进行配置。
    ssl_prefer_server_ciphers on;   
    location / {
        root html;   #站点目录。
        index index.html index.htm;   
    }
}

如无法访问请检查nginx所在机器的443端口是否打开

上一篇 下一篇

猜你喜欢

热点阅读