zoukankan      html  css  js  c++  java
  • 简单API接口签名验证

    前言

    后端在写对外的API接口时,一般会对参数进行签名来保证接口的安全性,在设计签名算法的时候,主要考虑的是这几个问题:
    1. 请求的来源是否合法
    2. 请求参数是否被篡改
    3. 请求的唯一性
    我们的签名加密也是主要针对这几个问题来实现
    

    设计

    基于上述的几个问题,我们来通过已下步骤来实现签名加密:
    1. 通过分配给APP对应的app_key和app_secret来验证身份
    2. 通过将请求的所有参数按照字母先后顺序排序后拼接再MD5加密老保证请求参数不被篡改
    3. 请求里携带时间戳参数老保证请求的唯一和过期,重复的请求在指定时间(可配置)内有效
    

    实现

    1. 签名生成:

      1. 生成当前时间戳timestamp=now
      2. 按照请求参数名的字母升序排列非空请求参数(包含accessKey)
        stringA="AccessKey=access&home=world&name=hello&work=java&timestamp=now&nonce=random";
      3. 拼接密钥accessSecret
        stringSignTemp="AccessKey=access&home=world&name=hello&work=java&timestamp=now&nonce=random&accessSecret=secret";
      4. MD5并转换为大写生成签名
        sign=MD5(stringSignTemp).toUpperCase();

    JAVA代码如下:params是从request里面获取的所有参数map,accessSecret是加密密钥

     private String createSign(Map<String, Object> params, String accessSecret) throws UnsupportedEncodingException {
            Set<String> keysSet = params.keySet();
            Object[] keys = keysSet.toArray();
            Arrays.sort(keys);
            StringBuilder temp = new StringBuilder();
            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);
                }
                temp.append(valueString);
            }
            temp.append("&").append(ACCESS_SECRET).append("=").append(accessSecret);
            return MD5Util.MD52(temp.toString()).toUpperCase();
        }
    
    1. 签名校验:

    拦截器部分代码

    • 参数格式校验
    • 超时校验
    • 验证签名
      public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
            Map<String, Object> result = new HashMap<String, Object>();
            String timestamp = request.getParameter(TIMESTAMP_KEY);
            String accessKey = request.getParameter(ACCESS_KEY);
            String accessSecret = map.get(accessKey);
    
            if (!org.apache.commons.lang.StringUtils.isNumeric(timestamp)) {
                result.put("code", 1000);
                result.put("msg", "请求时间戳不合法");
                WebUtils.writeJsonByObj(result, response, request);
                return false;
            }
    
            // 检查KEY是否合理
            if (StringUtils.isEmpty(accessKey) || StringUtils.isEmpty(accessSecret)) {
                result.put("code", 1001);
                result.put("msg", "加密KEY不合法");
                WebUtils.writeJsonByObj(result, response, request);
                return false;
            }
            Long ts = Long.valueOf(timestamp);
            // 禁止超时签名
            if (System.currentTimeMillis() - ts > SIGN_EXPIRED_TIME) {
                result.put("code", 1002);
                result.put("msg", "请求超时");
                WebUtils.writeJsonByObj(result, response, request);
                return false;
            }
    
            if (!verificationSign(request, accessKey, accessSecret)) {
                result.put("code", 1003);
                result.put("msg", "签名错误");
                WebUtils.writeJsonByObj(result, response, request);
                return false;
            }
            return true;
        }
    

    校验签名

     private boolean verificationSign(HttpServletRequest request, String accessKey, String accessSecret) throws UnsupportedEncodingException {
            Enumeration<?> pNames = request.getParameterNames();
            Map<String, Object> params = new HashMap<String, Object>();
            while (pNames.hasMoreElements()) {
                String pName = (String) pNames.nextElement();
                if (SIGN_KEY.equals(pName)) continue;
                Object pValue = request.getParameter(pName);
                params.put(pName, pValue);
            }
            String originSign = request.getParameter(SIGN_KEY);
            String sign = createSign(params, accessSecret);
            return sign.equals(originSign);
        }
    
    1. 完整代码:

    这里通过拦截器来实现接口拦截,可自行替换

    package com.mlcs.mop.common.web.interceptor;
    
    import com.mlcs.core.conf.ZKClient;
    import com.mlcs.mop.common.web.util.MD5Util;
    import com.mlcs.mop.common.web.util.WebUtils;
    import org.apache.zookeeper.KeeperException;
    import org.springframework.util.StringUtils;
    import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.StringReader;
    import java.io.UnsupportedEncodingException;
    import java.util.*;
    import java.util.concurrent.ConcurrentHashMap;
    
    /**
     * Author: Kelin
     * Date:  2018/5/16
     * Description:
     */
    @SuppressWarnings("SuspiciousMethodCalls")
    public class SimpleApiSignInterceptor extends HandlerInterceptorAdapter {
    
        // 签名超时时长,默认时间为5分钟,ms
        private static final int SIGN_EXPIRED_TIME = 5 * 60 * 1000;
    
        private static final String API_SIGN_KEY_CONFIG_PATH = "/mop/common/system/api_sign_key_mapping.properties";
    
        private static final String SIGN_KEY = "sign";
    
        private static final String TIMESTAMP_KEY = "timestamp";
    
        private static final String ACCESS_KEY = "accessKey";
    
        private static final String ACCESS_SECRET = "accessSecret";
    
        private static Map<String, String> map = new ConcurrentHashMap<String, String>();
    
    
        static {
            // 从zk加载key映射到内存里面
            try {
                String data = ZKClient.get().getStringData(API_SIGN_KEY_CONFIG_PATH);
                Properties properties = new Properties();
                properties.load(new StringReader(data));
                for (Object key : properties.keySet()) {
                    map.put(String.valueOf(key), properties.getProperty(String.valueOf(key)));
                }
            } catch (KeeperException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
    
        }
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
            Map<String, Object> result = new HashMap<String, Object>();
            String timestamp = request.getParameter(TIMESTAMP_KEY);
            String accessKey = request.getParameter(ACCESS_KEY);
            String accessSecret = map.get(accessKey);
    
            if (!org.apache.commons.lang.StringUtils.isNumeric(timestamp)) {
                result.put("code", 1000);
                result.put("msg", "请求时间戳不合法");
                WebUtils.writeJsonByObj(result, response, request);
                return false;
            }
    
            // 检查KEY是否合理
            if (StringUtils.isEmpty(accessKey) || StringUtils.isEmpty(accessSecret)) {
                result.put("code", 1001);
                result.put("msg", "加密KEY不合法");
                WebUtils.writeJsonByObj(result, response, request);
                return false;
            }
            Long ts = Long.valueOf(timestamp);
            // 禁止超时签名
            if (System.currentTimeMillis() - ts > SIGN_EXPIRED_TIME) {
                result.put("code", 1002);
                result.put("msg", "请求超时");
                WebUtils.writeJsonByObj(result, response, request);
                return false;
            }
    
            if (!verificationSign(request, accessKey, accessSecret)) {
                result.put("code", 1003);
                result.put("msg", "签名错误");
                WebUtils.writeJsonByObj(result, response, request);
                return false;
            }
            return true;
        }
    
        private boolean verificationSign(HttpServletRequest request, String accessKey, String accessSecret) throws UnsupportedEncodingException {
            Enumeration<?> pNames = request.getParameterNames();
            Map<String, Object> params = new HashMap<String, Object>();
            while (pNames.hasMoreElements()) {
                String pName = (String) pNames.nextElement();
                if (SIGN_KEY.equals(pName)) continue;
                Object pValue = request.getParameter(pName);
                params.put(pName, pValue);
            }
            String originSign = request.getParameter(SIGN_KEY);
            String sign = createSign(params, accessSecret);
            return sign.equals(originSign);
        }
    
        private String createSign(Map<String, Object> params, String accessSecret) throws UnsupportedEncodingException {
            Set<String> keysSet = params.keySet();
            Object[] keys = keysSet.toArray();
            Arrays.sort(keys);
            StringBuilder temp = new StringBuilder();
            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);
                }
                temp.append(valueString);
            }
            temp.append("&").append(ACCESS_SECRET).append("=").append(accessSecret);
            return MD5Util.MD52(temp.toString()).toUpperCase();
        }
    }
    
    
    千里之行,积于跬步;万里之船,成于罗盘,共勉。
  • 相关阅读:
    HDU 1594 find the max
    mongoose 数据库操作2
    我也来开发2048之终极奥义
    cocos2d-x 2.2.0 怎样在lua中注冊回调函数给C++
    windows常用运行命令
    机器学习——Pandas库
    搭建靶场环境
    Ubuntu快速安装MSF
    docker 基本使用
    linux下杀死进程
  • 原文地址:https://www.cnblogs.com/Kelin-/p/9475886.html
Copyright © 2011-2022 走看看