WebApi介面安全性 介面許可權調用、參數防篡改防止惡意調用

来源:https://www.cnblogs.com/buyixiaohan/archive/2019/10/31/11773088.html
-Advertisement-
Play Games

背景介紹 最近使用WebApi開發一套對外介面,主要是數據的外送以及結果回傳,介面沒什麼難度,採用WebApi+EF的架構簡單創建一個模板工程,使用template生成一套WebApi介面,去掉put、delete等操作,修改一下就可以上線。這些都不在話下,反正網上一大堆教程,隨便找那個step b... ...


背景介紹

最近使用WebApi開發一套對外介面,主要是數據的外送以及結果回傳,介面沒什麼難度,採用WebApi+EF的架構簡單創建一個模板工程,使用template生成一套WebApi介面,去掉put、delete等操作,修改一下就可以上線。這些都不在話下,反正網上一大堆教程,隨便找那個step by step做下來就可以了。

 

然後發佈上線後,介面是放在外網,面臨兩個問題:

  1. 如何保證介面的調用的合法性
  2. 如何保證介面及數據的安全性

其實這兩個問題是相互結合的,先保證合法,然後在合法基礎上保證請求的唯一性,避免參數被篡改。

鑒於介面上線期限緊迫,結合眾多案例,先解決掉介面調用數據的安全性問題,這裡採用了RSA報文加解密的方案,保證數據安全和防止介面被惡意調用以及參數篡改的問題。

本文參考博客園多篇博文,內容多有引用,文末附有參照博文的地址。

以下為正文!

正文

首先,介面面臨的問題:

  1. 請求來源(身份)是否合法(部分解決,後續在處理)
  2. 請求參數被篡改?
  3. 請求的唯一性(不可複製),防止請求被惡意攻擊

 

解決方案:

 

  1. 參數加密: 客戶端和服務端參數採用RSA加密後傳遞,原則上只有持有私鑰的服務端才能解密客戶端公鑰加密的參數,避免了參數篡改的問題
  2. 請求簽名:採用一套簽名演算法,對請求進行簽名驗證,保證請求的唯一性

 

這裡參照了WebAPi使用公鑰私鑰加密介紹和使用 一文,進行公鑰私鑰加解密的處理

先說服務端:

擴展 MessageProcessingHandler

先看一下MessageProcessingHandler的介紹:

#region 程式集 System.Net.Http, Version=4.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.2\System.Net.Http.dll
#endregion

using System.Threading;
using System.Threading.Tasks;

namespace System.Net.Http
{
    //
    // 摘要:
    //     僅對請求和/或響應消息進行一些小型處理的處理程式的基類。
    public abstract class MessageProcessingHandler : DelegatingHandler
    {
        //
        // 摘要:
        //     創建的一個實例 System.Net.Http.MessageProcessingHandler 類。
        protected MessageProcessingHandler();
        //
        // 摘要:
        //     創建的一個實例 System.Net.Http.MessageProcessingHandler 具有特定的內部處理程式類。
        //
        // 參數:
        //   innerHandler:
        //     內部處理程式負責處理 HTTP 響應消息。
        protected MessageProcessingHandler(HttpMessageHandler innerHandler);

        //
        // 摘要:
        //     處理每個發送到伺服器的請求。
        //
        // 參數:
        //   request:
        //     要處理的 HTTP 請求消息。
        //
        //   cancellationToken:
        //     可由其他對象或線程用以接收取消通知的取消標記。
        //
        // 返回結果:
        //     已處理的 HTTP 請求消息。
        protected abstract HttpRequestMessage ProcessRequest(HttpRequestMessage request, CancellationToken cancellationToken);
        //
        // 摘要:
        //     處理來自伺服器的每個響應。
        //
        // 參數:
        //   response:
        //     要處理的 HTTP 響應消息。
        //
        //   cancellationToken:
        //     可由其他對象或線程用以接收取消通知的取消標記。
        //
        // 返回結果:
        //     已處理的 HTTP 響應消息。
        protected abstract HttpResponseMessage ProcessResponse(HttpResponseMessage response, CancellationToken cancellationToken);
        //
        // 摘要:
        //     非同步發送 HTTP 請求到要發送到伺服器的內部處理程式。
        //
        // 參數:
        //   request:
        //     要發送到伺服器的 HTTP 請求消息。
        //
        //   cancellationToken:
        //     可由其他對象或線程用以接收取消通知的取消標記。
        //
        // 返回結果:
        //     表示非同步操作的任務對象。
        //
        // 異常:
        //   T:System.ArgumentNullException:
        //     request 是 null。
        protected internal sealed override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
    }
}

 

擴展這個類的目的是解密參數,其實也可以推遲到Action過濾器中做,但是還是覺得時機上在這裡處理比較合適。具體的建議瞭解一下WebApi消息管道以及擴展過濾器的相關文章,本文不再延伸。

下麵是擴展的實現代碼:

 

/// <summary>
    /// 請求預處理,報文解密
    /// </summary>
    /// <seealso cref="System.Net.Http.MessageProcessingHandler"/>
    public class ArgDecryptMessageProcesssingHandler : MessageProcessingHandler
    {

        /// <summary>
        /// 處理每個發送到伺服器的請求。
        /// </summary>
        /// <param name="request">          要處理的 HTTP 請求消息。</param>
        /// <param name="cancellationToken">可由其他對象或線程用以接收取消通知的取消標記。</param>
        /// <returns>已處理的 HTTP 請求消息。</returns>
        protected override HttpRequestMessage ProcessRequest(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var contentType = request.Content.Headers.ContentType;

            //swagger請求直接跳過不予處理
            if (request.RequestUri.AbsolutePath.Contains("/swagger"))
            {
                return request;
            }

            //獲得平臺私鑰
            string privateKey = Common.GetRsaPrivateKey();

            //獲取Get中的Query信息,解密後重置請求上下文
            if (request.Method == HttpMethod.Get)
            {
                string baseQuery = request.RequestUri.Query;
                if (!string.IsNullOrEmpty(baseQuery))
                {
                    baseQuery = baseQuery.Substring(1);
                    baseQuery = Regex.Match(baseQuery, "(sign=)*(?<sign>[\\S]+)").Groups[2].Value;
                    baseQuery = RsaHelper.RSADecrypt(privateKey, baseQuery);
                    var requestUrl = $"{request.RequestUri.AbsoluteUri.Split('?')[0]}?{baseQuery}";
                    request.RequestUri = new Uri(requestUrl);
                }

            }

            //獲取Post請求中body中的報文信息,解密後重置請求上下文
            if (request.Method == HttpMethod.Post)
            {
                string baseContent = request.Content.ReadAsStringAsync().Result;
                baseContent = Regex.Match(baseContent, "(sign=)*(?<sign>[\\S]+)").Groups[2].Value;
                baseContent = RsaHelper.RSADecrypt(privateKey, baseContent);
                request.Content = new StringContent(baseContent);
                //此contentType必須最後設置 否則會變成預設值
                request.Content.Headers.ContentType = contentType;
            }

            return request;
        }

        /// <summary>
        /// 處理來自伺服器的每個響應。
        /// </summary>
        /// <param name="response">         要處理的 HTTP 響應消息。</param>
        /// <param name="cancellationToken">可由其他對象或線程用以接收取消通知的取消標記。</param>
        /// <returns>已處理的 HTTP 響應消息。</returns>
        protected override HttpResponseMessage ProcessResponse(HttpResponseMessage response, CancellationToken cancellationToken)
        {
            return response;
        }
    }

獲取平臺私鑰那裡,實際上可以針對不同的介面調用方單獨一個,另起一篇在介紹。

 

然後找到解決方案【App_Start】目錄下的WebApiConfig類,在裡面添加如下代碼,啟用消息處理擴展類:

public static void Register(HttpConfiguration config)
        {
           

            // Web API 路由
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
            config.MessageHandlers.Add(new ArgDecryptMessageProcesssingHandler());


        }

擴展 ActionFilterAttribute

註意!註意!註意!

原博文中是擴展的 AuthorizeAttribute,即認證和授權過濾器,代碼實現上是沒有多大差別的;在時機上認證和授權過濾器要比方法過濾器執行的要早,更適合做認證和授權的操作。而我們擴展這個過濾器的目的是對報文進行簽名驗證以及超時驗證,所以使用方法過濾器更恰當些。

下麵是擴展過濾器的代碼:

/// <summary>
    /// 擴展方法過濾器,進入方法前驗證簽名
    /// </summary>
    public class ApiVerifyFilter : ActionFilterAttribute
    {

        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            base.OnActionExecuting(actionContext);

            //獲取平臺私鑰
            string privateKey = Common.GetRsaPrivateKey();

            //獲取請求的超時時間,為了測試設置為100秒,即兩次調用間隔不能超過100秒
            string expireyTime = ConfigurationManager.AppSettings["UrlExpireTime"];
            var request = actionContext.Request;

            //驗證簽名所需header內容
            if (!request.Headers.Contains("signature") || !request.Headers.Contains("timestamp") || !request.Headers.Contains("nonce"))
            {
                SetSpecialResponseMessage(actionContext, 40301);
                return;
            }
            var token = string.Empty;
            var signature = request.Headers.GetValues("signature").FirstOrDefault();
            var timeStamp = request.Headers.GetValues("timestamp").FirstOrDefault();
            var nonce = request.Headers.GetValues("nonce").FirstOrDefault();

            //驗證簽名
            if (!Common.SignValidate(privateKey, nonce, timeStamp, signature, token))
            {
                SetSpecialResponseMessage(actionContext, 40302);
                return;
            }
            //檢查介面調用是否超時
            var ts = Common.DateTime2TimeStamp(DateTime.UtcNow) - Convert.ToDouble(timeStamp);
            if (ts > int.Parse(expireyTime) * 1000)
            {
                SetSpecialResponseMessage(actionContext, 40303);
                return;
            }
        }

        /// <summary>
        /// 設置簽名驗證異常返回狀態
        /// </summary>
        /// <param name="actionContext">當前請求上下文</param>
        /// <param name="statusCode">異常狀態碼</param>
        private static void SetSpecialResponseMessage(HttpActionContext actionContext, int statusCode)
        {
            BizResponseModel model = new BizResponseModel
            {
                Status = statusCode,
                Date = DateTime.Now.ToString("yyyyMMddhhmmssfff"),
                Message = "服務端拒絕訪問"
            };
            switch (statusCode)
            {
                case 40301:
                    model.Message = "沒有設置簽名、時間戳、隨機字元串";
                    break;
                case 40302:
                    model.Message = "簽名無效";
                    break;
                case 40303:
                    model.Message = "無效的請求";
                    break;
                default:
                    break;
            }
            actionContext.Response = new HttpResponseMessage
            {
                Content = new StringContent(JsonConvert.SerializeObject(model))
            };
        }


        public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
        {
            base.OnActionExecuted(actionExecutedContext);
        }
    }

這裡為了方便寫了個ResponseModel,代碼如下:

/// <summary>
    /// 特殊狀態
    /// </summary>
    public class BizResponseModel
    {
        public int Status { get; set; }
        public string Message { get; set; }
        public string Date { get; set; }
    }

然後下麵是用的公共方法:

/// <summary>
        /// 獲取時間戳毫秒數
        /// </summary>
        /// <param name="dateTime"></param>
        /// <returns></returns>
        public static long DateTime2TimeStamp(DateTime dateTime)
        {
            TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
            return Convert.ToInt64(ts.TotalMilliseconds);
        }


        public static bool SignValidate(string privateKey, string nonce, string timestamp, string signature, string token)
        {
            bool isValidate = false;
            var tempSign = RsaHelper.RSADecrypt(privateKey, signature);
            string[] arr = new[] { token, timestamp, nonce }.OrderBy(z => z).ToArray();
            string arrString = string.Join("", arr);
            var sha256Result = arrString.EncryptSha256();
            if (sha256Result == tempSign)
            {
                isValidate = true;
            }
            return isValidate;
        }

簽名驗證的過程如下:

  1. 獲取到報文Header中的 nonce、timestamp、signature、token信息
  2. 將token、timestamp、nonce 三者合併數組中,然後進行順序排序(排序為了保證後續三個字元串拼接後一致)
  3. 將數組拼接成字元串,然後進行sha256 哈希運算(這裡隨便什麼運算都行,主要為了防止超長加密麻煩)
  4. 將上一步的哈希結果與[signature] RSA解密結果進行比對,一致則簽名驗證通過,否則則簽名不一致,請求為偽造

 


然後,現在需要啟用剛添加的方法過濾器,因為是繼承與屬性,可以全局啟用,或者單個Controller中啟用、或者為某個Action啟用。全局啟用代碼如下:

下的WebApiConfig類添加如下代碼:

config.Filters.Add(new ApiVerifyFilter());

 

OK,全部完成,最後附上兩個前後的效果對比!

網盤

 

參考博文:

WebApi安全性 使用TOKEN+簽名驗證

WebAPi介面安全之公鑰私鑰加密

使用OAuth打造webapi認證服務供自己的客戶端使用

Asp.Net WebAPI中Filter過濾器的使用以及執行順序

微信 公眾號開發文檔

 

寫博文太累了,回家吃螃蟹補補~


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

-Advertisement-
Play Games
更多相關文章
  • 設計模式可以使用我們在軟體開發過程中更加靈活,軟體的擴展更容易,軟體的耦合度更低,設計模式不是在開發中刻意去用的,而是到了什麼時候用什麼模式的,不能強迫的使用它,應該是自然而然的想到它。 單例模式 在23種設計模式中,單例最為簡單和純粹,也是最容易理解的,即它在軟體生命周期里,只有一個實例,就是說你 ...
  • 這兩個主題沒什麼關係,但是怕文章太短被移除主頁。 using聲明 using語句塊 這兩個主題沒什麼關係,但是怕文章太短被移除主頁。 using聲明 using語句塊 儘管.NET Core運行時有垃圾收集器(GC)來負責記憶體清理工作,但是我們還是要自己確保當非托管資源不再使用的時候應該被清理掉。以 ...
  • Json.NET常用方法彙總(可解決日常百分之90的需求) 0.Json.NET基礎用法 首先去官網下載最新的Newtonsoft.Json.dll(也可以使用VS自帶的NuGet搜索Json.NET下載(下載下圖第二個))並引用至項目。 (1)序列化實體類(將實體類對象序列化為Json字元串) 結 ...
  • 最近需要用到矩形相交演算法的簡單應用,所以特地拿一個很簡單的演算法出來供新手參考,為什麼說是給新手的參考呢因為這個演算法效率並不是很高,但是這個演算法只有簡簡單單的三行。程式使用了兩種方法來判斷是否重疊/相交,如果有興趣可以看一下,如果覺得有bug可以留言。代碼僅供參考。 C#中矩形的方法為Rectangl ...
  • 一、背景 因編程的基礎差,因此最近開始鞏固學習C#基礎,後期把自己學習的東西,總結相應文章中,有不足處請大家多多指教。 二、簡介 當我們程式出現問題時,我們通過斷點調試來追蹤找到問題點。 1.斷點調試: F10:逐步語句調試 F11:逐過程的調試 2.監視調試 3.即時視窗 三、實例 如圖所示: ...
  • 一、“老生常談”值類型與引用類型 眾所周知,.NET類型系統由 類、結構、枚舉、介面 和 委托 組成。而根據記憶體分配的方式來區分,所有的類型又被分為 值類型 與 引用類型。 一說到值類型,大多數人都會自信地說,“值類型不就是 int,float,double...還有...額...還有啥來著?”。然 ...
  • 一、背景 因編程的基礎差,因此最近開始鞏固學習C#基礎,後期把自己學習的東西,總結相應文章中,有不足處請大家多多指教。 二、簡介 變數:用來在電腦存儲數據 三、語法 第一種: 變數類型 變數名; 變數名=值; 描述:“=”號:在這不是等於的意思,是賦值的意思,表示把等號右邊的值賦值給左邊的變數。 ...
  • 地圖坐標系目前包括: 地球坐標 (WGS84) WGS84:World Geodetic System 1984,是為GPS全球定位系統使用而建立的坐標系統。 國際標準,從 GPS 設備中取出的數據的坐標系 國際地圖提供商使用的坐標系 火星坐標 (GCJ-02)也叫國測局坐標系 GCJ-02是由中國 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...