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(