• java安全入门篇之接口验签(原创)


    文章大纲

    一、加密与验签介绍
    二、接口验签实操
    三、项目源码下载

     

    一、加密与验签介绍

      大多数公共网络是不安全的,一切基于HTTP协议的请求/响应(Request or Response)都是可以被截获的、篡改、重放(重发)的。因此我们需要考虑以下几点内容:

    1. 防伪装攻击(案例:在公共网络环境中,第三方 有意或恶意 的调用我们的接口)
    2. 防篡改攻击(案例:在公共网络环境中,请求头/查询字符串/内容 在传输过程被修改)
    3. 防重放攻击(案例:在公共网络环境中,请求被截获,稍后被重放或多次重放)
    4. 防数据信息泄漏(案例:截获用户登录请求,截获到账号、密码等)

    二、接口验签实操

    1. 实操说明

      接口加密与验签的方法有非常多,比如RSA(后期进行讲解),基于token等方式,而对于普通项目,我认为最重要的是防伪装攻击、防篡改攻击、防重放攻击。因为接下来的实操,主要围绕以下几点进行。

    2. 逻辑讲解

    客户端操作
    (1)用户登录成功后,会接收到对应的key值和key过期时间,该key是经过32位小写加密,且编码格式为UTF-8
    (2)接口请求时,将请求的参数,通过key-value方式进行字典升序,且编码成UTF-8形式
    (3)将key值拼接在升序且编码后的字符串前面,进行MD32位小写加密,其编码成UTF-8形式形成签名,连同请求参数一同发送至后台
    (4)退出登录时,需要通知后台失效该用户的key
    (5)补充说明1:对于登录接口,如果检测到用户账号密码错误,则判断错误次数后,在一定时间内进行登录禁止,如果登录禁止解除后,用户再次出现错误,则延长限制时间
    (6)补充说明2:对于无需登录接口,需要限制客户端请求次数,进行接口防刷保护

    服务端操作
    (1)当用户登录成功时,生成与该用户对应的key值返回给用户端,同时将id与key缓存在redis中
    (2)当接收到请求时,根据请求id去redis查询对应key是多少,查不到则代表没有请求权限,将该用户系统信息,请求项目名、接口名,请求时间、错误类型(用户信息不正确/参数与签名不一致)存进redis(缓存进磁盘),当ip错误次数超过一定次数后,限制ip访问项目
    (3)将key和请求参数按客户端同样方式进行签名,与请求的sign进行比较
    (4)如果验签不一致,将该用户系统信息,请求项目名、接口名,请求时间、错误类型(用户信息不正确/参数与签名不一致)存进redis,当ip错误次数超过一定次数时,限制ip访问所有项目,若验签通过,则进行接口放行,且将用户系统信息,请求项目名、接口名,请求时间缓存进日志中(存进磁盘)

    Redis参数需记录信息
    1.用户信息:id,用户key,客户端请求系统信息
    2.验签错误信息:用户系统信息,请求项目名、接口名、请求时间、错误类型(用户信息不正确/参数与签名不一致)

    日志缓存信息
    接口请求成功信息:用户系统信息,请求项目名、接口名,请求时间

    3.代码讲解

    用户登录成功、生成key参数

    //模拟用户登录成功
    public String getMd5Key() {
    
            return "de456878b58568e29773e6a53b39d6ef";
        }
    

    获取客户端信息

    /**
     * 获取客户端的信息
     * @author 吴晓畅
     *
     */
    public final class SystemUtils {
        /**
         * 获取访问者IP
         * 在一般情况下使用Request.getRemoteAddr()即可,但是经过nginx等反向代理软件后,这个方法会失效。
         *
         * 本方法先从Header中获取X-Real-IP,如果不存在再从X-Forwarded-For获得第一个IP(用,分割),
         * 如果还不存在则调用Request .getRemoteAddr()。
         * @param request
         * @return
         */
        public  String getIpAddr(HttpServletRequest request) {
            String ip = request.getHeader("X-Real-IP");
            if (ip!= null && !"".equals(ip) && !"unknown".equalsIgnoreCase(ip)) {
                return ip;
            }
            ip = request.getHeader("X-Forwarded-For");
            if (ip!= null && !"".equals(ip)  && !"unknown".equalsIgnoreCase(ip)) {
                // 多次反向代理后会有多个IP值,第一个为真实IP。
                int index = ip.indexOf(',');
                if (index != -1) {
                    return ip.substring(0, index);
                } else {
                    return ip;
                }
            } else {
                return request.getRemoteAddr();
            }
        }
    
        /**
         * 获取来访者的浏览器版本
         * @param request
         * @return
         */
        public  String getRequestBrowserInfo(HttpServletRequest request){
            String browserVersion = null;
            String header = request.getHeader("user-agent");
            if(header == null || header.equals("")){
                return "";
            }
            if(header.indexOf("MSIE")>0){
                browserVersion = "IE";
            }else if(header.indexOf("Firefox")>0){
                browserVersion = "Firefox";
            }else if(header.indexOf("Chrome")>0){
                browserVersion = "Chrome";
            }else if(header.indexOf("Safari")>0){
                browserVersion = "Safari";
            }else if(header.indexOf("Camino")>0){
                browserVersion = "Camino";
            }else if(header.indexOf("Konqueror")>0){
                browserVersion = "Konqueror";
            }
            return browserVersion;
        }
    
        /**
         * 获取系统版本信息
         * @param request
         * @return
         */
        public  String getRequestSystemInfo(HttpServletRequest request){
            String systenInfo = null;
            String header = request.getHeader("user-agent");
            if(header == null || header.equals("")){
                return "";
            }
            //得到用户的操作系统
            if (header.indexOf("NT 6.0") > 0){
                systenInfo = "Windows Vista/Server 2008";
            } else if (header.indexOf("NT 5.2") > 0){
                systenInfo = "Windows Server 2003";
            } else if (header.indexOf("NT 5.1") > 0){
                systenInfo = "Windows XP";
            } else if (header.indexOf("NT 6.0") > 0){
                systenInfo = "Windows Vista";
            } else if (header.indexOf("NT 6.1") > 0){
                systenInfo = "Windows 7";
            } else if (header.indexOf("NT 6.2") > 0){
                systenInfo = "Windows Slate";
            } else if (header.indexOf("NT 6.3") > 0){
                systenInfo = "Windows 9";
            } else if (header.indexOf("NT 5") > 0){
                systenInfo = "Windows 2000";
            } else if (header.indexOf("NT 4") > 0){
                systenInfo = "Windows NT4";
            } else if (header.indexOf("Me") > 0){
                systenInfo = "Windows Me";
            } else if (header.indexOf("98") > 0){
                systenInfo = "Windows 98";
            } else if (header.indexOf("95") > 0){
                systenInfo = "Windows 95";
            } else if (header.indexOf("Mac") > 0){
                systenInfo = "Mac";
            } else if (header.indexOf("Unix") > 0){
                systenInfo = "UNIX";
            } else if (header.indexOf("Linux") > 0){
                systenInfo = "Linux";
            } else if (header.indexOf("SunOS") > 0){
                systenInfo = "SunOS";
            }
            return systenInfo;
        }
    
        /**
         * 获取来访者的主机名称
         * @param ip
         * @return
         */
        public  String getHostName(String ip){
            InetAddress inet;
            try {
                inet = InetAddress.getByName(ip);
                return inet.getHostName();
            } catch (UnknownHostException e) {
                e.printStackTrace();
            }
            return "";
        }
    
        /**
         * 命令获取mac地址
         * @param cmd
         * @return
         */
        private  String callCmd(String[] cmd) {
            String result = "";
            String line = "";
            try {
                Process proc = Runtime.getRuntime().exec(cmd);
                InputStreamReader is = new InputStreamReader(proc.getInputStream());
                BufferedReader br = new BufferedReader (is);
                while ((line = br.readLine ()) != null) {
                    result += line;
                }
            }catch(Exception e) {
                e.printStackTrace();
            }
            return result;
        }
        /**
         *
         *
         *
         * @param cmd
         *            第一个命令
         *
         * @param another
         *            第二个命令
         *
         * @return 第二个命令的执行结果
         *
         */
    
        private  String callCmd(String[] cmd,String[] another) {
            String result = "";
            String line = "";
            try {
                Runtime rt = Runtime.getRuntime();
                Process proc = rt.exec(cmd);
                proc.waitFor(); // 已经执行完第一个命令,准备执行第二个命令
                proc = rt.exec(another);
                InputStreamReader is = new InputStreamReader(proc.getInputStream());
                BufferedReader br = new BufferedReader (is);
                while ((line = br.readLine ()) != null) {
                    result += line;
                }
            }catch(Exception e) {
                e.printStackTrace();
            }
            return result;
        }
    
        /**
         *
         *
         *
         * @param ip
         *            目标ip,一般在局域网内
         *
         * @param sourceString
         *            命令处理的结果字符串
         *
         * @param macSeparator
         *            mac分隔符号
         *
         * @return mac地址,用上面的分隔符号表示
         *
         */
    
        private  String filterMacAddress(final String ip, final String sourceString,final String macSeparator) {
            String result = "";
            String regExp = "((([0-9,A-F,a-f]{1,2}" + macSeparator + "){1,5})[0-9,A-F,a-f]{1,2})";
            Pattern pattern = Pattern.compile(regExp);
            Matcher matcher = pattern.matcher(sourceString);
            while(matcher.find()){
                result = matcher.group(1);
                if(sourceString.indexOf(ip) <= sourceString.lastIndexOf(matcher.group(1))) {
                    break; // 如果有多个IP,只匹配本IP对应的Mac.
                }
            }
            return result;
        }
    
        /**
         * @param ip
         *            目标ip
         * @return Mac Address
         *
         */
    
        private  String getMacInWindows(final String ip){
            String result = "";
            String[] cmd = {"cmd","/c","ping " + ip};
            String[] another = {"cmd","/c","arp -a"};
            String cmdResult = callCmd(cmd,another);
            result = filterMacAddress(ip,cmdResult,"-");
            return result;
        }
        /**
         *
         * @param ip
         *            目标ip
         * @return Mac Address
         *
         */
        private  String getMacInLinux(final String ip){
            String result = "";
            String[] cmd = {"/bin/sh","-c","ping " +  ip + " -c 2 && arp -a" };
            String cmdResult = callCmd(cmd);
            result = filterMacAddress(ip,cmdResult,":");
            return result;
        }
    
        /**
         * 获取MAC地址
         *
         * @return 返回MAC地址
         */
        public  String getMacAddress(String ip){
            String macAddress = "";
            macAddress = getMacInWindows(ip).trim();
            if(macAddress==null||"".equals(macAddress)){
                macAddress = getMacInLinux(ip).trim();
            }
            return macAddress;
        }
    
        public  String getSystemMessage(HttpServletRequest request)
        {
    
            String ip = getIpAddr(request);
    
            String messsge = "IP地址为:" + ip + "&浏览器版本为:" + getRequestBrowserInfo(request) + "&系统版本为:" + getRequestSystemInfo(request)
                    + "&主机名称为:" + getHostName(ip) + "&MAC地址为:" + getMacAddress(ip);
    
            return messsge;
        }
    }
    

    排序算法

    /**
     * 对参数按key进行字典升序排列
     */
    public class SortUtils {
    
        /**
         * @param paraMap 参数
         * @param encode 编码
         * @param isLower 是否小写
         * @return
         */
        public static String formatUrlParam(Map<String, String> paraMap, String encode, boolean isLower) {
            String params = "";
            Map<String, String> map = paraMap;
    
            try {
                List<Entry<String, String>> itmes = new ArrayList<Entry<String, String>>(map.entrySet());
    
                //对所有传入的参数按照字段名从小到大排序
                //Collections.sort(items); 默认正序
                //可通过实现Comparator接口的compare方法来完成自定义排序
                Collections.sort(itmes, new Comparator<Entry<String, String>>() {
                    @Override
                    public int compare(Entry<String, String> o1, Entry<String, String> o2) {
                        // TODO Auto-generated method stub
                        return (o1.getKey().toString().compareTo(o2.getKey()));
                    }
                });
    
                //构造URL 键值对的形式
                StringBuffer sb = new StringBuffer();
                for (Entry<String, String> item : itmes) {
                    if (StringUtils.isNotBlank(item.getKey())) {
                        String key = item.getKey();
                        String val = item.getValue();
                        val = URLEncoder.encode(val, encode);
                        if (isLower) {
                            sb.append(key.toLowerCase() + "=" + val);
                        } else {
                            sb.append(key + "=" + val);
                        }
                        sb.append("&");
                    }
                }
    
                params = sb.toString();
                if (!params.isEmpty()) {
                    params = params.substring(0, params.length() - 1);
                }
            } catch (Exception e) {
                return "";
            }
            return params;
        }
    }
    

    MD5加密算法

    public class MD5Utils {
    
        private static final String hexDigIts[] = {"0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"};
    
        /**
         * MD5加密
         * @param origin 字符
         * @param charsetname 编码
         * @return
         */
        public static String MD5Encode(String origin, String charsetname){
            String resultString = null;
            try{
                resultString = new String(origin);
                MessageDigest md = MessageDigest.getInstance("MD5");
                if(null == charsetname || "".equals(charsetname)){
                    resultString = byteArrayToHexString(md.digest(resultString.getBytes()));
                }else{
                    resultString = byteArrayToHexString(md.digest(resultString.getBytes(charsetname)));
                }
            }catch (Exception e){
            }
            return resultString;
        }
    
    
        public static String byteArrayToHexString(byte b[]){
            StringBuffer resultSb = new StringBuffer();
            for(int i = 0; i < b.length; i++){
                resultSb.append(byteToHexString(b[i]));
            }
            return resultSb.toString();
        }
    
        public static String byteToHexString(byte b){
            int n = b;
            if(n < 0){
                n += 256;
            }
            int d1 = n / 16;
            int d2 = n % 16;
            return hexDigIts[d1] + hexDigIts[d2];
        }
    
    }
    

    对客户端请求参数进行加签

    /**
         * 进行加签
         *
         * @param key 用户的key
         *
         * @param valueMap 需要签名的集合,未处理前的
         * @return 处理后,返回的签名值
         */
        public String getSign(String key, Map<String, String>  valueMap)
        {
    
            String soreValueMap = SortUtils.formatUrlParam(valueMap, "utf-8", true);//对参数按key进行字典升序排列
    
            String signVlue = key + soreValueMap;//将key拼接在请求参数的前面
    
            String md5SignVlues = MD5Utils.MD5Encode(signVlue, "utf8");//形成MD5加密后的签名
    
            return md5SignVlues;
    
        }
    

    进行验签

     /**
         * 进行验签操作
         *
         * @param valueMap 请求参数
         *
         * @param sign 接口调用方传过来的sign
         *
         * @return 验签成功返回true  否则返回false
         */
        public boolean verifySign(Map<String, String>  valueMap, String sign)
        {
    
            System.out.println("服务器接收签名为:"+sign);
    
            String soreValueMap = SortUtils.formatUrlParam(valueMap, "utf-8", true);//对参数按key进行字典升序排列
    
            String signVlue = getMd5Key() + soreValueMap;//将key拼接在请求参数的前面
    
            String md5SignVlues = MD5Utils.MD5Encode(signVlue, "utf8");//形成MD5加密后的签名
    
            System.out.println("服务端处理得到签名为:"+md5SignVlues);
    
            if(md5SignVlues.equals(sign))
            {
                return true;
            }
    
            return false;
        }
    

    测试签名算法

    @Test
    //    public void testSigm(HttpServletRequest request)
        public void testSigm()
        {
    
    //        SystemUtils systemUtils = new SystemUtils();
    //
    //        System.out.println(systemUtils.getSystemMessage(request));
    
            Map<String, String> map = new HashMap<String, String>();
    
            map.put("a", "200");
    
            map.put("title", "测试标题");
    
            map.put("content", "测试内容");
    
            map.put("order_no","1807160812023");
    
            Map<String, String> map2 = new HashMap<String, String>();
    
            map2.put("a", "200");
    
            map2.put("title", "测试标题");
    
            map2.put("content", "测试内容");
    
            map2.put("order_no","1807160812023");
    
            String sign = getSign(getMd5Key(), map);
    
            System.out.println(verifySign(map2, sign));
    
        }
    

    运行结果如下所示:

     

    三、项目源码下载

    链接:https://pan.baidu.com/s/1vgUxjtRY-V5TlqHjTcKDBw
    提取码:qy38

     

  • 相关阅读:
    Python实现DES加密算法
    空循环,g++ O2优化
    java 高并发下超购问题解决
    原型模式
    Lambda速学
    观察者模式
    略读策略模式
    .net 字典的速学
    执行计划准备篇
    关于“策略模式”与“桥接模式”的问题
  • 原文地址:https://www.cnblogs.com/WUXIAOCHANG/p/10544569.html
Copyright © 2020-2023  润新知