共用方式為


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 Forms 應用程式或 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.");
    }
}