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

 

猜你喜欢

转载自www.cnblogs.com/WUXIAOCHANG/p/10544569.html