java安全入門篇之介面驗簽

来源:https://www.cnblogs.com/WUXIAOCHANG/archive/2019/03/16/10544569.html
-Advertisement-
Play Games

文章大綱 一、加密與驗簽介紹二、介面驗簽實操三、項目源碼下載 一、加密與驗簽介紹 大多數公共網路是不安全的,一切基於HTTP協議的請求/響應(Request or Response)都是可以被截獲的、篡改、重放(重發)的。因此我們需要考慮以下幾點內容: 防偽裝攻擊(案例:在公共網路環境中,第三方 有 ...


文章大綱

一、加密與驗簽介紹
二、介面驗簽實操
三、項目源碼下載

 

一、加密與驗簽介紹

  大多數公共網路是不安全的,一切基於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

 

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 學Python這麼久了,一直想做個界面出來,最近發現Python有個內置庫tkinter,利用它可以很輕鬆做出一些簡易的UI界面 關註公眾號「**Python專欄**」,後臺回覆關鍵字:**zsxq03**,獲取本文全部代碼 ...
  • 在《Java編程思想》中關於泛型的講解中,提到了自限定類型: 作者說道: 這就像兩面鏡子彼此照向對方所引起的目眩效果一樣,是一種無限反射。 類接受泛型參數 ,而 由一個邊界限定,這個邊界就是擁有T作為其參數的 接下來,作者用了近3頁的紙來進行解釋這個問題,且語言極其晦澀難懂(可能是翻譯的問題),但是 ...
  • 第一問:TCP與UDP的區別 參考答案: 1.基於連接與無連接 2.TCP要求系統資源較多,UDP較少; 3.UDP程式結構較簡單 4.流模式(TCP)與數據報模式(UDP); 5.TCP保證數據正確性,UDP可能丟包 6.TCP保證數據順序,UDP不保證 考點:聽說騰訊必考TCP,反正TCP的三次 ...
  • 1. 模板字元串簡介: 顧名思義,模板字元串是用來定義一個模板是使用的,就像Vue,React中的template語法。 首先,先來瞭解一下template string的基本用法: 在ES5中,我們大多都有過拼串的經歷吧。 模板字元串的語法是反引號(`` --> 鍵盤左上角),利用反引號將字元串封 ...
  • 題目鏈接:http://acm.hdu.edu.cn/showproblem.php?pid=6467 看到這題,簡單數學???對不起我給數學老師丟臉了! 這裡解釋一下第二步到第三步:假設n=3,第二步{1*C(1,1)+1*C(1,2)+1*C(1,3)+2*C(2,2)+2*C(2,3)+3*C ...
  • java基礎--提示對話框的使用 2019-03-17-00:35:50 雲林原創 一、顯示信息對話框:使用“JOptionPane.showMessageDialog”顯示: 使用實例: 1、顯示錯誤類型對話框: 視圖: 2、傳達信息類型對話框: 視圖: 3、警告對話框: 視圖: 4、提問對話框: ...
  • java基礎--常用函數總結 2019-3-16-23:28:01 雲林原創 1、split()字元串分割函數 將一個字元串分割為子字元串,然後將結果作為字元串數組返回。 2、Math.floor( )舍掉小數取整數 3、Math.rint( )四捨五入取整數 4、Math.ceil( )進位取整數 ...
  • 下午在刷題過程中,忽然想寫2048了,以彌補以前寫的那個千多行的,所以簡單思考了一下準備採取的數據結構就開始了,本以為一個小時能搞定,結果後面改bug還是多花了些時間。因為在醫院,所以聲音不敢太大,如果看,建議耳機+聲音最大,可以考慮倍速。個人感覺用C寫這些東西的意義在於,你去掉了一些花里胡哨的東西 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...