1. 程式人生 > >Java Servlet 實戰入門教程-19-servlet web 安全

Java Servlet 實戰入門教程-19-servlet web 安全

web 安全的幾個方面

認證、授權、機密性、資料完整性。

特徵

web 應用包含的資源可以被多個使用者訪問。這些資源常常不受保護的遍歷,開放網路如 Internet。

在這樣的環境,大量的 web 應用將有安全需求。

儘管質量保障和實現細節可能會有所不同,但 servlet 容器有滿足這些需求的機制和基礎設施,共用如下一些特性:

  • 身份認證:表示通訊實體彼此證明他們具體身份的行為是被授權訪問的。

  • 資源訪問控制:表示和資源的互動是受限於集合的使用者或為了強制完整性、保密性、或可用性約束的程式。

  • 資料完整性:表示用來證明資訊在傳輸過程中沒有被第三方修改。

  • 保密或資料隱私:表示用來保證資訊只對以授權訪問的使用者可用。

登入和登出

容器在分派請求到 servlet引擎之前建立呼叫者身份。

在整個請求處理過程中或直到應用成功的在請求上呼叫身份認證、登入或退出,呼叫者身份保持不變。

對於非同步請求,呼叫者身份建立在初始分派時,直到整個請求處理完成或直到應用成功的在請求上呼叫身份認證、登入或退出,呼叫者身份保持不變。

在處理請求時登入到一個應用,精確地對應有一個有效的非空的與請求關聯的呼叫者身份,可以通過呼叫請求的 getRemoteUser 或getUserPrincipal 確定。

這些方法的任何一個返回null值表示呼叫者沒有登入到處理請求的應用。

容器可以建立 HTTP Session 物件用於跟蹤登入狀態。

如果開發人員建立一個 session 而使用者沒有進行身份認證,然後容器認證使用者,登入後,對開發人員程式碼可見的 session 必須是相同的 session 物件,該session 是登入發生之前建立的,以便不丟失 session 資訊。

宣告式安全

宣告式安全是指以在應用外部的形式表達應用的安全模型需求,包括角色、訪問控制和認證需求。部署描述符是web應用中的宣告式安全的主要手段。

部署人員對映應用的邏輯安全需求到特定於執行時環境的安全策略的表示。在執行時,servlet 容器使用安全策略表示來實施認證和授權。

安全模型適用於 web 應用的靜態內容部分和客戶端請求到的應用內的servlet 和過濾器。

安全模型不適用於當 servlet 使用 RequestDispatcher 呼叫靜態內容或使用 forward 或 include 到的servlet。

程式設計式安全

當僅僅宣告式安全是不足以表達應用的安全模型時,程式設計式安全被用於意識到安全的應用。

介面方法

程式設計式安全包括以下 HttpServletRequest 介面的方法:

  • authenticate

authenticate 方法允許應用由容器發起在一個不受約束的請求上下文內的來訪者請求認證。

  • login

login 方法允許應用執行使用者名稱和密碼收集(作為一種 Form-Based Login 的替代)。

  • logout

logout 方法提供用於應用重置來訪者的請求身份。

  • getRemoteUser

getRemoteUser 方法由容器返回與該請求相關的遠端使用者(即來訪者)的名字。

  • isUserInRole

isUserInRole 方法確定是否與該請求相關的遠端使用者(即來訪者)在一個特定的安全形色中。

isUserInRolem 方法需要一個引用應用角色的引數。

對於用在呼叫isUserInRole 的每一個單獨的角色引用,一個帶有關聯到角色引用的role-name 的 security-role-ref 元素應該宣告在部署描述符中。

每一個 security-role-ref 元素應該包含一個 role-link 子元素,其值是應用內嵌的角色引用連結到的應用安全形色名稱。

容器使用 security-role-ref 的 role-name 是否等於角色引用來決定哪一個 security-role 用於測試使用者是否在身份中。

  • getUserPrincipal

getUserPrincipal 方法確定遠端使用者(即來訪者)的 Principal 名稱並返回一個與遠端使用者相關的 java.security.Principal 物件。呼叫getUserPrincipal 返回的 Principal 的 getName 方法返回遠端使用者的名字。

這些 API 允許 Servlet 基於獲得的資訊做一些業務邏輯決策。

如果沒有使用者通過身份認證,getRemoteUser 方法返回 null,isUserInRole 方法總返回 false,getUserPrincipal 方法總返回null。

例如,對映安全形色應用“FOO”到 role-name 為"manager"的安全形色的語法是:

<security-role-ref>
    <role-name>FOO</role-name>
    <role-link>manager</role-link>
</security-role-ref>

在這種情況下,如果屬於“manager”安全形色的使用者呼叫了 servlet,則呼叫 isUserInRole(“FOO”) API的結果是true。

如果用於呼叫 isUserInRole 的一個角色引用,沒有匹配的 security-role-ref 存在 ,容器必須預設以 security-role 的 role-name 等於用於呼叫的角色引用來測試使用者身份。

角色名 * 應該從不用作呼叫 isUserInRole 的引數。

任何以*呼叫isUserInRole 必須返回 false。

如果 security-role 的 role-name 使用**測試,且應用沒有宣告一個role-name為**的應用 security-role,isUserInRole 必須僅返回 true。

如果使用者已經認證;即,僅當 getRemoteUser 和 getUserPrincipal 將同時返回非 null 值。否則,容器必須檢查使用者身份是否在應用角色中。

security-role-ref 元素宣告通知部署人員應用使用的角色引用和必須定義哪一個對映。

程式設計式安全策略配置

@ServletSecurity 註解

@ServletSecurity 提供了用於定義訪問控制約束的另一種機制,相當於那些通過在行動式部署描述符中宣告式或通過 ServletRegistration 介面的 setServletSecurity 方法程式設計式表示。

Servlet 容器必須支援在實現 javax.servlet.Servlet 介面的類(和它的子類)上使用@ServletSecurity 註解。

@HttpConstraint

@HttpConstraint 註解用在 @ServletSecurity 中表示應用到所有 HTTP 協議方法的安全約束,且 HTTP 協議方法對應的@HttpMethodConstraint 沒有出現在 @ServletSecurity 註解中。

對於一個 @HttpConstraint 返回所有預設值發生在與至少一個@HttpMethodConstraint 返回不同於所有預設值的組合的特殊情況,@HttpMethodConstraint 表示沒有安全約束被應用到任何 HTTP 協議方法,否則一個安全約束將應用。

這個例外是確保這些潛在的非特定@HttpConstraint 使用沒有產生約束,這將明確建立不受保護的訪問這些方法;因為,它們沒有被約束覆蓋。

@HttpMethodConstraint

@HttpMethodConstraint 註解用在 @ServletSecurity 註解中表示在特定 HTTP 協議訊息上的安全約束。

@ServletSecurity 註解可以指定在(更準確地說,目標是) Servlet 實現類上,且根據 @Inherited 元註解定義的規則,它的值是被子類繼承的。至多隻有一個 @ServletSecurity 註解例項可以出現在 Servlet 實現類上,且 @ServletSecurity 註解必須不指定在(更準確地說,目標是) Java 方法上。

當一個或多個 @HttpMethodConstraint 註解定義在 @ServletSecurity註解中時,每一個 @HttpMethodConstraint 定義的 security-constraint,其應用到 @HttpMethodConstraint 中標識的 HTTP 協議方法。除了它的 @HttpConstraint 返回所有預設值、和它包含至少一個返回不同於所有預設值的 @HttpMethodConstraint 的情況之外,@ServletSecurity 註解定義另一個 security-constraint 應該到所有還沒有定義相關 @HttpMethodConstraint 的 HTTP 協議方法。

感想

這裡的定義較為複雜,建議參考拓展閱讀中的鑑權框架。

角色

安全形色是由應用開發人員或裝配人員定義的邏輯使用者分組。當部署了應用,由部署人員對映角色到執行時環境的 principal 或組。

Servlet 容器根據 principal 的安全屬性為與進入請求相關的principal 實施宣告式或程式設計式安全。 這可能以如下任一方式發生:

部署人員已經對映一個安全形色到執行環境中的一個使用者組。呼叫的principal 所屬的使用者組取自其安全屬性。僅當 principal 所屬的使用者組由部署人員已經映射了安全形色,principal 是在安全形色中。

部署人員已經對映安全形色到安全策略域中的 principal 名字。

在這種情況下,呼叫的 principal 的名字取自其安全屬性。僅當 principal 名字與安全形色已對映到的 principal 名字一樣時,principal 是在安全形色中。

四種認證型別

web客戶端可以使用以下機制之一向web伺服器認證使用者身份:

  • HTTP Basic Authentication(HTTP基本認證)

  • HTTP Digest Authentication(HTTP摘要認證)

  • HTTPS Client Authentication(HTTPS客戶端認證)

  • Form Based Authentication(基於表單的認證)

HTTP Basic Authentication

HTTP Basic Authentication 基於使用者名稱和密碼,是 HTTP/1.0 規範中定義的認證機制。Web 伺服器要求 web 客戶端認證使用者。作為請求的一部分,web 伺服器傳遞 realm(字串)給要被認證的使用者。Web 客戶端獲取使用者的使用者名稱和密碼並傳給 web 伺服器。Web 伺服器然後在指定的realm 認證使用者。

基本認證是不安全的認證協議。使用者密碼以簡單的 base64 編碼傳送,且未認證目標伺服器。額外的保護可以減少一些擔憂:安全傳輸機制(HTTPS),或者網路層安全(如 IPSEC 協議或 VPN 策略)被應用到一些部署場景。

HTTP Digest Authentication

跟 HTTP Basic Authentication 類似,HTTP Digest Authentication 也是 基於使用者名稱和密碼,所不同的是,HTTP Digest Authentication 並不在網路中傳遞使用者密碼。在 HTTP Digest Authentication 中,客戶端傳送單向雜湊的密碼(和額外的資料)。儘管密碼不線上路上發生,HTTP Digest Authentication 需要對認證容器可用的明文密碼等價物(密碼等價物可以是這樣的,它們僅能在一個特定的 realm 用來認證使用者),以致容器可以通過計算預期的摘要驗證接收到的認證者。Servlet容器應支援 HTTP_DIGEST 身份認證。

Form Based Authentication

“登入介面”的外觀在使用 web 瀏覽器的內建的認證機制時不能被改變。本規範引入了所需的基於表單的認證機制,允許開發人員控制登入介面的外觀。

Web 應用部署描述符包含登入表單和錯誤頁面條目。登入介面必須包含用於輸入使用者名稱和密碼的欄位。這些欄位必須分別命名為 j_username 和j_password。

當用戶試圖訪問一個受保護的 web 資源,容器堅持使用者的認證。如果使用者已經通過認證則具有訪問資源的許可權,請求的 web 資源被啟用並返回一個引用。如果使用者未被認證,發生所有如下步驟:

  1. 與安全約束關聯的登入介面被髮送到客戶端,且 URL 路徑和 HTTP 協議方法觸發容器儲存的認證。

  2. 使用者被要求填寫表單,包括使用者名稱和密碼欄位。

  3. 客戶端 post 表單到伺服器。

  4. 容器嘗試使用來自表單的資訊認證使用者。

  5. 如果認證失敗,使用 forward 或 redirect 返回錯誤頁面,且響應狀態碼設定為200。錯誤頁面包含失敗資訊。

  6. 如果授權成功,客戶端使用儲存的 URL 路徑重定向到資源。

  7. 當一個重定向的和已認證的請求到達容器,容器恢復請求和 HTTP 協 議方法,且已認證的使用者主體被檢檢視看是否它在已授權的允許訪問資源的角色中。

  8. 如果使用者已授權,容器處理接受的請求。

到達步驟7的重定向的請求的 HTTP 協議方法,可以和觸發認證的請求有不同的 HTTP 方法。

同樣地,在第6步的重定向之後,表單認證器必須處理重定向的請求,即使對到達請求的 HTTP 方法的認證不是必需的。

為了改善重定向的請求的 HTTP 方法的可預測性,容器應該使用303狀態碼(SC_SEE_OTHER)重定向(步驟6),除了與HTTP 1.0使用者代理的協作之外的是必需的;在這種情況應該使用302狀態碼。

當進行一個不受保護的傳輸時,基於表單的認證受制於一些與基本驗證一樣的相同的脆弱性。

當觸發認證的請求在一個安全傳輸之上到達,或者登入頁面受制於一個CONFIDENTIAL user-data-constraint,登入頁面必須返回給使用者,並在安全傳輸之上提交到容器。

登入頁面受制於一個CONFIDENTIAL user-data-constraint,且一個CONFIDENTIAL user-data-constraint應該包含在每一個包含認證要求的security-constraint中。

HttpServletRequest 介面的 login 方法提供另一種用於應用控制它的登入介面外觀的手段。

登入表單

基於表單的登入和基於 URL 的 session 跟蹤可以通過程式設計實現。基於表單的登入應該僅被用在當 session 由 cookie 或 SSL session 資訊維護時。

為了進行適當的認證,登入表單的 action 總是 j_security_check。

該限制使得不管請求什麼資源,登入表單都能工作,且避免了要求伺服器指定輸出表單的 action 欄位。登入表單應該在密碼錶單欄位上指定autocomplete=“off”。

下面的示例展示瞭如何把表單編碼到HTML頁中:

<form method=”POST” action=”j_security_check”>
<input type=”text” name=”j_username”>
<input type=”password” name=”j_password” autocomplete=”off”>
</form>

如果因為 HTTP 請求造成基於表單的登入被呼叫,容器必須儲存原始請求引數,在成功認證時使用,它重定向呼叫所請求的資源。

如果使用者已使用表單登入通過認證,且已經建立一個 HTTP session,該session 的超時或失效將導致使用者被登出,在這種情況下,隨後的請求必須導致使用者重新認證。登出與認證具有相同的作用域:例如,如果容器支援單點登入,如 Java EE 技術相容的web容器,使用者只需要與託管在web容器中的任何一個 web 應用重新認證即可。

HTTPS Client Authentication

使用HTTPS(HTTP over SSL)認證終端使用者是一種強認證機制。該機制需要客戶端擁有 Public Key Certificate(PKC)。目前,PKC 在電子商務應用中是很有用的,也對瀏覽器中的單點登入很有用。

其他容器認證機制

Servlet 容器應該提供公共介面,可用於整合和配置其他的 HTTP 訊息層的認證機制,提供給代表已部署應用的容器使用。這些介面應該提供給參與者使用而不是容器供應商(包括應用開發人員、系統管理人員和系統整合人員)。

為了便於實現和整合其他容器認證機制,建議為所有 Servlet 容器實現Servlet 容器 Profile 的 Java 認證 SPI(即,JSR 196)。SPI可下載地址:http://www.jcp.org/en/jsr/detail?id=196

拓展閱讀

實際使用中,上面的認證過程實在過於繁瑣,建議使用成熟的框架。

許可權驗證框架

shiro

伺服器跟蹤認證資訊

下面的安全標識(如使用者和組)在執行時環境中對映的角色是環境指定的而非應用指定的,理想的是:

  1. 使登入機制和策略是 web 應用部署到的環境屬性。

  2. 在同一個容器部署的所有應用能使用相同的認證資訊來表示principal,且

  3. 需要重新認證使用者僅當已經越過了安全策略域邊界。

因此,servlet 容器需要在容器級別(而不是在 web 應用級別)跟蹤認證資訊。

這允許在一個 web 應用已經通過認證的使用者可以訪問容器管理的以同樣的安全標識許可的其他資源。

預設策略

預設情況下,身份認證並不需要訪問資源。

當安全約束(如果有)包含的url-pattern 是請求 URI 的最佳匹配,且結合了施加在請求的 HTTP 方法上的 auth-constraint(指定的角色),則身份認證是需要的。

同樣,一個受保護的傳輸是不需要的,除非應用到請求的安全約束結合了施加在請求的HTTP方法上的 user-data-constraint(有一個受保護的transport-guarantee)。

參考資料

web 安全

《Head First Servlet & JSP》

教程導航

教程彙總