Share via


SignalR 安全性簡介

作者: Patrick FletcherTom FitzMacken

警告

本檔不適用於最新版的 SignalR。 請查看ASP.NET Core SignalR

本文說明開發 SignalR 應用程式時必須考慮的安全性問題。

本主題中使用的軟體版本

本主題的舊版

如需舊版 SignalR 的相關資訊,請參閱 SignalR 舊版

問題和批註

請留下您喜歡本教學課程的意見反應,以及我們可以在頁面底部的批註中改善的內容。 如果您有與教學課程不直接相關的問題,您可以將問題張貼到 ASP.NET SignalR 論壇StackOverflow.com

概觀

本文件包含下列章節:

SignalR 安全性概念

驗證和授權

SignalR 不提供驗證使用者的任何功能。 相反地,您可以將 SignalR 功能整合到應用程式的現有驗證結構中。 您可以像平常在應用程式中一樣驗證使用者,並在 SignalR 程式碼中使用驗證的結果。 例如,您可以使用 ASP.NET 表單驗證來驗證使用者,然後在中樞中強制執行哪些使用者或角色有權呼叫方法。 在您的中樞中,您也可以將使用者名稱或使用者是否屬於角色的驗證資訊傳遞給用戶端。

SignalR 提供 Authorize 屬性,以指定哪些使用者可以存取中樞或方法。 您可以將 Authorize 屬性套用至中樞或中樞中的特定方法。 如果沒有 Authorize 屬性,中樞上的所有公用方法都可供連線到中樞的用戶端使用。 如需中樞的詳細資訊,請參閱 SignalR 中樞的驗證和授權

您會將 屬性套用至中 Authorize 樞,但不會套用持續性連線。 若要在使用 時強制執行授權規則, PersistentConnection 您必須覆寫 AuthorizeRequest 方法。 如需持續性連線的詳細資訊,請參閱 SignalR 持續性連線的驗證和授權

連線權杖

SignalR 藉由驗證寄件者的身分識別,來降低執行惡意命令的風險。 針對每個要求,用戶端和伺服器會傳遞連接權杖,其中包含已驗證使用者的連線識別碼和使用者名稱。 連接識別碼可唯一識別每個連線的用戶端。 伺服器會在建立新的連接時隨機產生連接識別碼,並在連線期間保存該識別碼。 Web 應用程式的驗證機制會提供使用者名稱。 SignalR 會使用加密和數位簽章來保護連線權杖。

此圖顯示從用戶端新增連接要求到伺服器接收連線要求到用戶端接收回應之伺服器回應的箭號。驗證系統會在 [回應] 和 [已接收的回應] 方塊中產生連線權杖。

針對每個要求,伺服器會驗證權杖的內容,以確保要求來自指定的使用者。 使用者名稱必須對應至連線識別碼。藉由驗證連線識別碼和使用者名稱,SignalR 可防止惡意使用者輕鬆地模擬其他使用者。 如果伺服器無法驗證連線權杖,要求就會失敗。

此圖顯示從用戶端要求到伺服器接收要求到已儲存權杖的箭號。連線權杖和訊息同時位於 [用戶端] 方塊和 [伺服器] 方塊中。

因為連線識別碼是驗證程式的一部分,所以您不應該向其他使用者顯示一位使用者的連線識別碼,或將值儲存在用戶端上,例如在 Cookie 中。

連線權杖與其他權杖類型

安全性工具偶爾會標示連線權杖,因為它們似乎為會話權杖或驗證權杖,這會在公開時造成風險。

SignalR 的連線權杖不是驗證權杖。 它用來確認發出此要求的使用者與建立連線的使用者相同。 連線權杖是必要的,因為 ASP.NET SignalR 允許在伺服器之間移動連線。 權杖會將連線與特定使用者產生關聯,但不會判斷發出要求之使用者的身分識別。 若要正確驗證 SignalR 要求,它必須有一些其他權杖來判斷提示使用者的身分識別,例如 Cookie 或持有人權杖。 不過,連線權杖本身不會提出要求是由該使用者提出的宣告,只有權杖中包含的連線識別碼與該使用者相關聯。

由於連線權杖不會提供自己的驗證宣告,因此不會被視為「會話」或「驗證」權杖。 取得指定使用者的連線權杖,並在以不同使用者身分驗證的要求中重新執行它 (或未經驗證的要求) 將會失敗,因為要求的使用者身分識別和儲存在權杖中的身分識別不相符。

重新連線時重新加入群組

根據預設,SignalR 應用程式會在從暫時中斷重新連線時自動將使用者重新指派給適當的群組,例如在連線逾時之前卸載並重新建立連線。重新連線時,用戶端會傳遞包含連線識別碼和指派群組的群組權杖。 群組權杖會以數位方式簽署並加密。 重新連線之後,用戶端會保留相同的連線識別碼;因此,從重新連線的用戶端傳遞的連接識別碼必須符合用戶端所使用的先前連線識別碼。 此驗證可防止惡意使用者在重新連線時傳遞要求以加入未經授權的群組。

不過,請務必注意,群組權杖不會過期。 如果使用者過去屬於群組,但該群組遭到禁用,該使用者可能會模擬包含禁止群組的群組權杖。 如果您需要安全地管理哪些使用者屬於哪些群組,您必須將該資料儲存在伺服器上,例如在資料庫中。 然後,將邏輯新增至您的應用程式,以驗證使用者是否屬於群組。 如需驗證群組成員資格的範例,請參閱 使用群組

只有在暫時中斷後重新連線時,才會自動重新加入群組。 如果使用者從應用程式離開或應用程式重新開機中斷連線,您的應用程式必須處理如何將該使用者新增至正確的群組。 如需詳細資訊,請參閱 使用群組

SignalR 如何防止跨網站偽造要求

跨網站偽造要求 (CSRF) 是攻擊,惡意網站會將要求傳送至使用者目前登入的易受攻擊網站。 SignalR 會防止 CSRF,因為惡意網站不太可能為您的 SignalR 應用程式建立有效的要求。

CSRF 攻擊的描述

以下是 CSRF 攻擊的範例:

  1. 使用者使用表單驗證登入 www.example.com

  2. 伺服器會驗證使用者。 來自伺服器的回應包含驗證 Cookie。

  3. 若未登出,使用者就會流覽惡意網站。 此惡意網站包含下列 HTML 格式:

    <h1>You Are a Winner!</h1>
    <form action="http://example.com/api/account" method="post">
        <input type="hidden" name="Transaction" value="withdraw" />
        <input type="hidden" name="Amount" value="1000000" />
        <input type="submit" value="Click Me"/>
    </form>
    

    請注意,表單動作張貼到易受攻擊的網站,而不是惡意網站。 這是 CSRF 的「跨網站」部分。

  4. 使用者按一下 [提交] 按鈕。 瀏覽器包含具有要求的驗證 Cookie。

  5. 要求會在具有使用者驗證內容的 example.com 伺服器上執行,而且可以執行已驗證使用者允許執行的任何動作。

雖然此範例需要使用者按一下表單按鈕,但惡意頁面可能就像輕鬆地執行將 AJAX 要求傳送至 SignalR 應用程式的腳本一樣。 此外,使用 SSL 不會防止 CSRF 攻擊,因為惡意網站可以傳送「HTTPs://」要求。

一般而言,CSRF 攻擊可能會針對使用 Cookie 進行驗證的網站,因為瀏覽器會將所有相關 Cookie 傳送至目的地網站。 不過,CSRF 攻擊不限於惡意探索 Cookie。 例如,基本和摘要式驗證也很容易遭受攻擊。 當使用者以基本或摘要式驗證登入之後,瀏覽器會自動傳送認證,直到會話結束為止。

SignalR 所採取的 CSRF 風險降低

SignalR 會採取下列步驟,以防止惡意網站對您的應用程式建立有效的要求。 SignalR 預設會採取這些步驟,您不需要在程式碼中採取任何動作。

  • 停用跨網域要求 SignalR 會停用跨網域要求,以防止使用者從外部網域呼叫 SignalR 端點。 SignalR 會將來自外部網域的任何要求視為無效,並封鎖要求。 建議您保留此預設行為;否則,惡意網站可能會讓使用者將命令傳送至您的網站。 如果您需要使用跨網域要求,請參閱 如何建立跨網域連線
  • 在查詢字串中傳遞連接權杖,而不是 Cookie SignalR 會將連線權杖當作查詢字串值傳遞,而不是作為 Cookie。 將連線權杖儲存在 Cookie 不安全,因為瀏覽器在遇到惡意程式碼時,可能會不小心轉送連線權杖。 此外,在查詢字串中傳遞連接權杖可防止連接權杖保存在目前連接之外。 因此,惡意使用者無法在其他使用者的驗證認證下提出要求。
  • 驗證連線權杖連線權杖 一節所述,伺服器知道哪些連線識別碼與每個已驗證的使用者相關聯。 伺服器不會處理不符合使用者名稱的連接識別碼的任何要求。 惡意使用者不太可能猜測有效的要求,因為惡意使用者必須知道使用者名稱和目前的隨機產生的連線識別碼。該連線識別碼會在連接結束後立即變成無效。 匿名使用者不應存取任何敏感性資訊。

SignalR 安全性建議

安全通訊端層 (SSL) 通訊協定

SSL 通訊協定會使用加密來保護用戶端與伺服器之間的資料傳輸。 如果您的 SignalR 應用程式在用戶端與伺服器之間傳輸敏感性資訊,請使用 SSL 進行傳輸。 如需設定 SSL 的詳細資訊,請參閱 如何在 IIS 7 上設定 SSL

請勿使用群組作為安全性機制

群組是收集相關使用者的便利方式,但它們不是限制敏感性資訊存取的安全機制。 當使用者在重新連線期間自動重新加入群組時,這特別正確。 相反地,請考慮將具特殊許可權的使用者新增至角色,並將中樞方法的存取限制為只有該角色的成員。 如需根據角色限制存取的範例,請參閱 SignalR 中樞的驗證和授權。 如需重新連線時檢查使用者存取群組的範例,請參閱 使用群組

安全地處理來自用戶端的輸入

若要確保惡意使用者不會將腳本傳送給其他使用者,您必須將用於廣播給其他用戶端之用戶端的所有輸入編碼。 您應該在接收用戶端上編碼訊息,而不是伺服器,因為您的 SignalR 應用程式可能會有許多不同類型的用戶端。 因此,HTML 編碼適用于 Web 用戶端,但不適用於其他類型的用戶端。 例如,用來顯示聊天訊息的 Web 用戶端方法會藉由呼叫 html() 函式安全地處理使用者名稱和訊息。

chat.client.addMessageToPage = function (name, message) {
    // Html encode display name and message. 
    var encodedName = $('<div />').text(name).html();
    var encodedMsg = $('<div />').text(message).html();
    // Add the message to the page. 
    $('#discussion').append('<li><strong>' + encodedName
        + '</strong>:  ' + encodedMsg + '</li>');
};

與使用中連線對使用者狀態的變更進行協調

如果使用者的驗證狀態在作用中連線存在時變更,使用者會收到錯誤,指出「使用者身分識別無法在作用中的 SignalR 連線期間變更」。在此情況下,您的應用程式應該重新連線到伺服器,以確定連線識別碼和使用者名稱是協調的。 例如,如果您的應用程式允許使用者在作用中連線存在時登出,則連線的使用者名稱將不再符合下一個要求傳入的名稱。 您會想要在使用者登出之前停止連線,然後重新開機它。

不過,請務必注意,大部分的應用程式都不需要手動停止和啟動連線。 如果您的應用程式會在登出之後將使用者重新導向至個別頁面,例如Web Form應用程式或 MVC 應用程式中的預設行為,或在登出後重新整理目前頁面,則作用中的連線會自動中斷連線,而且不需要任何其他動作。

下列範例示範如何在使用者狀態變更時停止和啟動連線。

<script type="text/javascript">
    $(function () {
        var chat = $.connection.sampleHub;
        $.connection.hub.start().done(function () {
            $('#logoutbutton').click(function () {
                chat.connection.stop();
                $.ajax({
                    url: "Services/SampleWebService.svc/LogOut",
                    type: "POST"
                }).done(function () {
                    chat.connection.start();
                });
            });
        });
    });
</script>

或者,如果您的網站搭配表單驗證使用滑動到期,而且沒有任何活動可讓驗證 Cookie 保持有效,則使用者的驗證狀態可能會變更。 在此情況下,使用者將會登出,而且使用者名稱將不再符合連線權杖中的使用者名稱。 您可以新增一些腳本,定期要求網頁伺服器上的資源,讓驗證 Cookie 保持有效,以修正此問題。 下列範例示範如何每隔 30 分鐘要求資源一次。

$(function () {
    setInterval(function() {
        $.ajax({
            url: "Ping.aspx",
            cache: false
        });
    }, 1800000);
});

自動產生的 JavaScript Proxy 檔案

如果您不想在每個使用者的 JavaScript Proxy 檔案中包含所有中樞和方法,您可以停用自動產生檔案。 如果您有多個中樞和方法,但不希望每個使用者知道所有方法,則可以選擇此選項。 您可以將 EnableJavaScriptProxies 設定為 false來停用自動產生。

var hubConfiguration = new HubConfiguration();
hubConfiguration.EnableJavaScriptProxies = false;
app.MapSignalR(hubConfiguration);

如需 JavaScript Proxy 檔案的詳細資訊,請參閱 產生的 Proxy 及其用途

例外狀況

您應該避免將例外狀況物件傳遞至用戶端,因為物件可能會向用戶端公開敏感性資訊。 相反地,請在用戶端上呼叫方法,以顯示相關的錯誤訊息。

public Task SampleMethod()
{
    try
    { 
        // code that can throw an exception
    }
    catch(Exception e)
    {
        // add code to log exception and take remedial steps

        return Clients.Caller.DisplayError("Sorry, the request could not be processed.");
    }
}