java安全入门篇之接口验签
文章大纲
一、加密与验签介绍
二、接口验签实操
三、项目源码下载
一、加密与验签介绍
大多数公共网络是不安全的,一切基于http协议的请求/响应(request or response)都是可以被截获的、篡改、重放(重发)的。因此我们需要考虑以下几点内容:
- 防伪装攻击(案例:在公共网络环境中,第三方 有意或恶意 的调用我们的接口)
- 防篡改攻击(案例:在公共网络环境中,请求头/查询字符串/内容 在传输过程被修改)
- 防重放攻击(案例:在公共网络环境中,请求被截获,稍后被重放或多次重放)
- 防数据信息泄漏(案例:截获用户登录请求,截获到账号、密码等)
二、接口验签实操
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)); }
运行结果如下所示:
三、项目源码下载