1. 概述
在傳統的 Web 應用程序中,登錄通常需要將用户名和密碼發送到服務器進行身份驗證。雖然這些元素在 GET 請求中的 URL 參數方面在理論上可行,但將它們封裝到 POST 請求中顯然更好。
然而,由於登出功能不需要發送任何敏感信息,因此是否可以通過 GET 請求提供登出功能呢?
在本教程中,我們將探討這一設計考慮的各個方面。
2. 服務器端會話
當管理服務器端會話時,必須暴露一個端點來銷燬這些會話。我們可能會受到 GET 方法的簡單性所吸引。當然,這在技術上可以工作,但可能會導致一些不良行為。
某些進程,例如 Web 加速器,會為用户預取 GET 鏈接。預取鏈接的目的是立即為用户點擊該鏈接時提供內容,從而縮短頁面加載時間。這些進程假設 GET 鏈接僅用於返回內容,而不是更改任何狀態。
如果我們將登出操作作為 GET 請求暴露,並將其呈現為鏈接,這些進程可能會無意中在嘗試預取頁面鏈接時將用户登出。
如果我們的登出 URL 不是靜態可用的,例如由 JavaScript 確定,則可能不是問題。但是,HTTP/1.1 RFC 明確指出,GET 方法僅應用於返回內容,並且用户不能對 GET 請求的副作用負責。我們應在可能的情況下遵循此建議。
相比之下,RFC 將 POST 方法描述為可以提交數據(我們的會話或會話 ID)到數據處理進程(登出)的方法。這更準確地描述了我們試圖實現的目標。
2.1. Spring Security
默認情況下,Spring Security 要求登出請求為 POST 類型。但是,我們可以通過禁用 CSRF 保護來使 Spring 使用 GET 類型的登出請求:
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
...
}3. 無狀態 REST
當管理無狀態 REST 會話時,“註銷”的概念會發生變化。在無狀態環境中,每個請求都包含完整的會話信息。因此,通過使用 JavaScript 簡單地丟棄會話,而不是發送任何請求,就可以實現“註銷”操作。
然而,出於安全原因,服務器仍應被通知註銷操作,以便黑名單被撤銷的 JWT。這可以防止在“註銷”後使用會話。
即使在無狀態環境中,註銷時仍必須向服務器發送請求。 由於此類請求的意圖並非檢索內容,因此不應使用 GET 方法。相反,會話應通過 POST 方法明確地發送給服務器,以指示註銷操作。
4. 結論
在本次簡要討論中,我們簡要探討了登出操作應使用 GET 還是 POST 的常見設計問題。
我們考慮了以下幾個方面:
- 語義上,GET 請求不應產生任何狀態相關的副作用
- 用户可能正在瀏覽器中運行的某些進程可能會包含預取鏈接。如果登出操作通過 GET 進行,預取進程可能會意外地在登錄後將用户登出
- 即使是無狀態會話也應向服務器報告登出事件,並且應通過 POST 請求進行