java安全入門篇之接口驗籤(原創)

文章大綱

1、加密與驗籤介紹
2、接口驗籤實操
3、項目源碼下載javascript

 

1、加密與驗籤介紹

  大多數公共網絡是不安全的,一切基於HTTP協議的請求/響應(Request or Response)都是能夠被截獲的、篡改、重放(重發)的。所以咱們須要考慮如下幾點內容:php

  1. 防假裝攻擊(案例:在公共網絡環境中,第三方 有意或惡意 的調用咱們的接口)
  2. 防篡改攻擊(案例:在公共網絡環境中,請求頭/查詢字符串/內容 在傳輸過程被修改)
  3. 防重放攻擊(案例:在公共網絡環境中,請求被截獲,稍後被重放或屢次重放)
  4. 防數據信息泄漏(案例:截獲用戶登陸請求,截獲到帳號、密碼等)

2、接口驗籤實操

1. 實操說明

  接口加密與驗籤的方法有很是多,好比RSA(後期進行講解),基於token等方式,而對於普通項目,我認爲最重要的是防假裝攻擊、防篡改攻擊、防重放攻擊。由於接下來的實操,主要圍繞如下幾點進行。java

2. 邏輯講解

客戶端操做
(1)用戶登陸成功後,會接收到對應的key值和key過時時間,該key是通過32位小寫加密,且編碼格式爲UTF-8
(2)接口請求時,將請求的參數,經過key-value方式進行字典升序,且編碼成UTF-8形式
(3)將key值拼接在升序且編碼後的字符串前面,進行MD32位小寫加密,其編碼成UTF-8形式造成簽名,連同請求參數一同發送至後臺
(4)退出登陸時,須要通知後臺失效該用戶的key
(5)補充說明1:對於登陸接口,若是檢測到用戶帳號密碼錯誤,則判斷錯誤次數後,在必定時間內進行登陸禁止,若是登陸禁止解除後,用戶再次出現錯誤,則延長限制時間
(6)補充說明2:對於無需登陸接口,須要限制客戶端請求次數,進行接口防刷保護nginx

服務端操做
(1)當用戶登陸成功時,生成與該用戶對應的key值返回給用戶端,同時將id與key緩存在redis中
(2)當接收到請求時,根據請求id去redis查詢對應key是多少,查不到則表明沒有請求權限,將該用戶系統信息,請求項目名、接口名,請求時間、錯誤類型(用戶信息不正確/參數與簽名不一致)存進redis(緩存進磁盤),當ip錯誤次數超過必定次數後,限制ip訪問項目
(3)將key和請求參數按客戶端一樣方式進行簽名,與請求的sign進行比較
(4)若是驗籤不一致,將該用戶系統信息,請求項目名、接口名,請求時間、錯誤類型(用戶信息不正確/參數與簽名不一致)存進redis,當ip錯誤次數超過必定次數時,限制ip訪問全部項目,若驗籤經過,則進行接口放行,且將用戶系統信息,請求項目名、接口名,請求時間緩存進日誌中(存進磁盤)redis

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)); } 

運行結果以下所示:

 

3、項目源碼下載

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

相關文章
相關標籤/搜索