[譯] 從 Java EE 8 Security API 開始 —— 第一部分

從 Java EE 8 Security API 開始 —— 第一部分

面向雲和微服務平臺的 Java 企業級安全

新的 HttpAuthenticationMechanism、IdentityStore 和 SecurityContext 接口概述html

關於這個系列:前端

期待已久的 Java EE Security API (JSR 375) 將 Java 企業級安全帶入雲計算和微服務的新紀元。本系列的文章將向您展現如何簡化新的安全機制,以及 Java EE 跨容器安全的標準化處理,而後在啓用雲的項目中使用它們。java

經驗豐富的 Java™ 開發者應該瞭解,Java 並不會受到缺少 Java 安全機制的影響。可選的方案有 Java 容器受權協議說明 (JACC),Java 身份認證服務提供器 (JASPIC),以及大量第三方特定於容器的安全 API 和配置管理解決方案。android

問題不在於缺少選擇,而在於缺少企業標準。沒有標準,致使幾乎沒有什麼能夠激勵供應商始終如一地實現核心特性,好比,身份驗證,像上下文和依賴注入(CDI)以及表達式語言(EL)那樣獨有解決方案的新技術更新,或者與雲和微服務架構的安全發展保持同步。ios

本系列介紹了新的 Java EE Security API,首先會概述 API 及其三個主要接口:HttpAuthenticationMechanismIdentityStoreSecurityContextgit

獲取代碼github

Java EE 新的安全標準

Java EE 安全規範的開發得力於 2014 Java EE 8 問卷調查,社區的反饋推進了 Java EE 安全規範的開發步伐。簡化和標準化 Java 企業級安全是許多調查對象優先考慮的事項。JSR 375專家組一旦成立,將肯定如下問題:web

  • 構成 Java EE 的各類 EJB 和 servlet 容器定義了相似的與安全相關的 API,但語法存在細微差異。例如,servlet 檢查用戶角色時,調用 HttpServletRequest.isUserInRole(String role),而 EJB 則調用 EJBContext.isCallerInRole(String roleName)
  • 實現像 JACC 這樣的現有安全機制,困難重重,而 JASPIC 也很難被正確使用。
  • 現有機制沒法充分利用現代 Java EE 的編程特性,例如上下文和依賴注入(CDI)。
  • 沒有可移值性方法來控制如何在後端跨容器時,進行身份驗證。
  • 對於管理標識存儲或者角色和權限的配置,沒有標準的支持。
  • 對於部署自定義身份驗證規則,沒有標準支持。

這些是 JSR 375 旨在解決的主要問題。同時,該規範經過定義用於身份驗證、身份存儲、角色和權限以及跨容器受權的可移值性 API,促使開發者可以自行管理和控制安全性。sql

Java EE Security API 的優勢在於它提供了一種配置身份存儲和身份驗證機制的替代方法,但並不能取代現有的安全機制。Java EE Security API 容許開發人員以一致的和可移值的方式啓用 Java EE web 應用程序的安全性 —— 不管是否具備特定於供應商的或者獨有的解決方案。數據庫

Java EE Security API 中有什麼?

Java EE Security API 1.0 版本包含了初始提交草案的一個子集,並且側重於本地雲應用程序相關的技術。這些特性是:

  • 用於身份驗證的 API
  • 標識存儲 API
  • 上下文安全的 API

這些特性與全部 Java EE 安全實現的新的標準化術語結合在一塊兒。剩餘的特性(計劃包含在下一個版本中)是:

  • 密碼別名 API
  • 角色/權限分配 API
  • 受權攔截器 API

Web 安全認證

Java EE 平臺已經指定了兩種用於驗證 Web 應用程序用戶的機制:Servlet 4.0 (JSR 369) 提供適用於通常應用程序配置的聲明式機制。對於健壯性有更高需求的場景,JASPIC 定義了一個叫做 ServerAuthModule 的服務提供者接口,它支持開發認證模塊來處理任何憑證類型。此外,Servlet 容器配置文件指定了如何將 JASPIC 與 servlet 容器集成。

這兩種機制都是有意義和有效的,但對於 web 應用程序開發者來講,每種機制都存在其自身的侷限性。

Servlet 容器機制被限制爲只支持 Servlet 4.0 定義的小部分憑據類型,並且它沒法支持與調用方的複雜交互。它也沒法爲應用程序提供一種方法,以肯定調用者是根據所需的標識存儲進行身份驗證的。

相反,JASPIC 很是優秀,並且有很好的延展性,但它的使用也至關複雜。編碼 AuthModule,而且將其與 web 容器對齊以進行身份驗證使用,可能會很是難以處理。除此之外,JASPIC 沒有聲明式配置,也沒有明確的方式來重載註冊 AuthModule 的編碼方式。

Java EE Security API 經過一個新的接口 HttpAuthenticationMechanism 解決了其中一些問題。新接口本質上是 JASPIC ServerAuthModule 接口的一個簡化版 servlet 容器變體,它利用了現有的機制,同時削弱了它們的限制。

HttpAuthenticationMechanism 實例是容器負責提供注入的 CDI bean。HttpAuthenticationMechanism 接口的其餘實現能夠由應用程序或 servlet 容器提供。注意,HttpAuthenticationMechanism 僅爲 servlet 容器指定。

對 Servlet 4.0 身份驗證的支持

Java EE 容器必須爲 Servlet 4.0 規範中定義的三種身份認證機制提供 HttpAuthenticationMechanism 實現。這三種實現是:

  • 基本 HTTP 身份驗證(第 13.6.1 章節)
  • 基於表單的身份驗證(第 13.6.3 章節)
  • 自定義表單身份驗證(第 13.6.3.1 章節)

每一個實現都由相關注解的存在觸發:

  • @BasicAuthenticationMechanismDefinition
  • @FormAuthenticationMechanismDefinition
  • @CustomFormAuthenticationMechanismDefinition

當遇到這些註解之一時,容器會實例化相關機制的實例,並使其當即可用。

在新規範中,再也不須要像 Servlet 4.0 所要求的那樣,在 web.xml 中的 <login-config> 元素之間指定身份驗證機制。事實上,若是 web.xml 和基於 HttpAuthentication 機制的註解同時存在時,部署過程可能會失敗 —— 至少要忽略 web.xml 配置。

讓咱們看看每種機制的示例是如何運行的。

基本的 HTTP 身份驗證

@BasicAuthenticationMechanismDefinition 註解觸發 Servlet 4.0 定義的基本 HTTP 身份驗證。清單 1 列舉了一個示例。惟一的配置參數是可選的,並且容許指定 realm。

清單 1. 基本的 HTTP 身份驗證
@BasicAuthenticationMechanismDefinition(realmName="${'user-realm'}")
@WebServlet("/user")
@DeclareRoles({ "admin", "user", "demo" })
@ServletSecurity(@HttpConstraint(rolesAllowed = "user"))
public class UserServlet extends HttpServlet { … }
複製代碼

什麼是 realm?

服務器資源能夠劃分爲單獨的受保護控件。在這種狀況下,每一個用戶都將擁有本身的身份驗證模式和受權數據庫,其中包含受同源策略控制的用戶和組。這個用戶和組的數據庫稱爲 realm

基於表單的身份驗證

@FormAuthenticationMechanismDefinition 註解用於基於表單的身份驗證。它有一個必要的參數 loginToContinue,用於配置 web 應用程序的登陸頁面、錯誤頁面和重定向或轉發特性。在清單 2 中,您能夠看到登陸頁面是用 URL 定義的,useForwardToLoginExpression 是使用表達式語言(EL)配置的。不須要向 @LoginToContinue 註解傳遞任何參數,由於實現會提供默認值。

清單 2. 基於表單的身份驗證
@FormAuthenticationMechanismDefinition(
   loginToContinue = @LoginToContinue(
       loginPage="/login-servlet",
       errorPage="/error",
       useForwardToLoginExpression="${appConfig.forward}"
   )
)
@ApplicationScoped
public class ApplicationConfig { ... }
複製代碼

自定義表單認證

@CustomFormAuthenticationMechanismDefinition 註解觸發內置自定義表單身份驗證。清單 3 給出了一個示例。

清單 3. 自定義表單認證
@CustomFormAuthenticationMechanismDefinition(
   loginToContinue = @LoginToContinue(
       loginPage="/login.do"
   )
)
@WebServlet("/admin")
@DeclareRoles({ "admin", "user", "demo" })
@ServletSecurity(@HttpConstraint(rolesAllowed = "admin"))
public class AdminServlet extends HttpServlet { ... }
複製代碼

自定義表單身份驗證旨在更好地與 JavaServer Pages (JSF) 和相關的 Java EE 技術保持一致性。login.do 頁面顯示後,用戶名和密碼由登陸頁面的後臺 bean 輸入並處理。

IdentityStore API

標識存儲是存儲用戶標識數據的數據庫,如用戶名、組成員和用於驗證的憑據信息。Java EE Security API 提供了一個名爲 IdentityStore 的抽象標識存儲。相似於 JAAS LoginModule 接口,IdentityStore 用於與標識存儲進行交互,以便對用戶進行身份驗證並檢索組成員身份。

正如規範所描述的,IdentityStoreHttpAuthenticationMechanism 的實現所使用,但這不是必須的, IdentityStore 能夠獨立存在,供任何其餘身份驗證機制使用。儘管如此,使用 IdentityStoreHttpAuthenticationMechanism 使應用程序可以以可移植和標準化的方式控制用於身份驗證的身份存儲,在大部分用例場景中,都推薦使用。

IdentityStore API 包括一個 IdentityStoreHandler 接口,HttpAuthenticationMechanism 必須委託它來驗證用戶憑據。以後,IdentityStoreHandler 調用 IdentityStore 實例。Identity 存儲實現不是直接使用的,而是經過專門的處理程序進行交互的。

IdentityStoreHandler 能夠針對多個 IdentityStores 進行身份驗證,而且以 CredentialValidationResult 實例的形式返回聚合結果。不管憑據是否有效,該對象可能只具備傳遞憑據的做用,或者它能夠是包含下述任何信息的豐富對象:

  • CallerPrincipal
  • 主體所屬的一組集合
  • 調用者的名稱或者 LDAP 可分辨的名稱
  • 標識存儲中調用方的惟一標識

標識存儲按順序進行查詢,這取決於每一個 IdentityStore 實現的優先級。存儲列表被解析了兩次:首先用於身份驗證,而後用於受權。

做爲開發者,您能夠經過實現 IdentityStore 接口來實現本身的輕量級標識存儲,或者您可使用爲 LDAP 和 RDBMS 內置的 IdentityStores 的其中一種。它們是經過將配置細節傳遞給適當的註解來初始化的 —— @LdapIdentityStoreDefinition 或者 @DataBaseIdentityStoreDefinition

配置內置的 IdentityStore

最簡單的標識存儲是數據庫存儲。它是經過 @DataBaseIdentityStoreDefinition 註解進行配置的。正如清單 4 所演示的那樣,這兩個內置的數據存儲註解基於 Java EE 7 中已有的 @DataStoreDefinition 註解。

清單 4 演示瞭如何配置數據庫身份存儲。這些配置選項自己就進行了自我解釋,並且若是您曾經配置過數據庫定義,應該會很熟悉。

清單 4. 配置數據庫標識存儲
@DatabaseIdentityStoreDefinition(
   dataSourceLookup = "${'java:global/permissions_db'}",
   callerQuery = "#{'select password from caller where name = ?'}",
   groupsQuery = "select group_name from caller_groups where caller_name = ?",
   hashAlgorithm = PasswordHash.class,
   priority = 10
)
@ApplicationScoped
@Named
public class ApplicationConfig { ... }
複製代碼

注意,清單 4 中的優先級要設置爲 10。在發現多個標識存儲並肯定相對於其餘存儲的迭代順序時使用。數目越少,優先級越高。

LDAP 的配置如清單 5 所描述的那樣,很是簡單。若是您有 LDAP 語義配置方面的經驗,您會發現這裏的選項很是熟悉。

清單 5. 配置 LDAP 標識存儲
@LdapIdentityStoreDefinition(
   url = "ldap://localhost:33389/",
   callerBaseDn = "ou=caller,dc=jsr375,dc=net",
   groupSearchBase = "ou=group,dc=jsr375,dc=net"
)
@DeclareRoles({ "admin", "user", "demo" })
@WebServlet("/admin")
public class AdminServlet extends HttpServlet { ... }
複製代碼

自定義 IdentityStore

設計您本身的輕量級標識存儲很是簡單。您須要實現 IdentityStore 接口,至少要實現 validate() 方法。接口上有四種方法,它們都有默認的實現方式。validate() 方法是運行標識存儲所需的最小條件。它接受 Credential 實例,而後返回 CredentialValidationResults 實例。

在清單 6 中,validate() 方式接收一個包含要驗證的登陸憑據的 UsernamePasswordCredential 實例,而後返回一個 CredentialValidationResults 的實例。若是簡單的配置邏輯促使身份驗證成功,則使用用戶名和用戶所屬組配置該對象。若是身份驗證失敗,那麼 CredentialValidationResults 實例只包含狀態標誌 INVALID

清單 6. 定製化的輕量級標識存儲
@ApplicationScoped
public class LiteWeightIdentityStore implements IdentityStore {
   public CredentialValidationResult validate(UsernamePasswordCredential userCredential) {
       if (userCredential.compareTo("admin", "pwd1")) {
           return new CredentialValidationResult("admin", 
		       new HashSet<>(asList("admin", "user", "demo")));
       }
       return INVALID_RESULT;
   }
}
複製代碼

注意,實現是基於 @ApplicationScope 註解的。這是必需的,由於 IdentityStoreHandler 保存對 CDI 容器管理的全部 IdentityStore bean 實例的引用。@ApplicationScope 註解確保實例是 CDI 管理的 bean,該 bean 實例對整個應用程序來講,都是可用的。

要使用您本身輕量級標識存儲,您能夠向自定義 HttpAuthenticationMechanism 注入 IdentityStoreHandler,就像清單 7 演示的那樣。

清單 7. 向自定義 HttpAuthenticationMechanism 注入 LiteWeightIdentityStore
@ApplicationScoped
public class LiteAuthenticationMechanism implements HttpAuthenticationMechanism {
   @Inject
   private IdentityStoreHandler idStoreHandler;
   @Override
   public AuthenticationStatus validateRequest(HttpServletRequest req, 
											   HttpServletResponse res, 
											   HttpMessageContext context) {
       CredentialValidationResult result = idStoreHandler.validate(
               new UsernamePasswordCredential(
                       req.getParameter("name"), req.getParameter("password")));
       if (result.getStatus() == VALID) {
           return context.notifyContainerAboutLogin(result);
       } else {
           return context.responseUnauthorized();
       }
   }
}
複製代碼

SecurityContext API

IdentityStoreHttpAuthenticationMechanism 將用戶的身份驗證和受權完美結合,可是自身的聲明式模型還沒有成型。程序的安全性編碼使 web 應用程序能執行受權或拒絕訪問應用程序資源所需的檢查,SecurityContext API 提供了這一功能性需求。

目前,Java EE 容器在實現安全上下文對象的方式上並不一致。例如,servlet 容器提供一個 HttpServletRequest 實例,在該實例上調用 getUserPrincipal() 方法來獲取表示用戶身份的 UserPrincipal。EJB 容器提供了不一樣命名的 EJBContext 實例,在該實例上調用同名方法。一樣的,若是須要測試用戶是否屬於某個角色,則必須在 HttpServletRequest 實例上調用 isUserRole() 方法,而後在 EJBContext 實例上調用 isCallerInRole()

什麼是上下文安全

在 Java 企業級應用程序中,上下文安全 提供了對與當前通過身份驗證的用戶關聯的安全相關信息的訪問。SecurityContext API 的目標是在全部 servlet 和 EJB 容器中提供對應應用程序安全上下文的訪問一致性。

新的 SecurityContext 提供了跨 Java EE 容器的一致性機制,用於獲取身份驗證和受權信息。新的 Java EE Security 規範要求至少在 servlet 和 EJB 容器中使用 SecurityContext。服務器供應商也能夠在使其在其餘容器中可用。

SecurityContext 接口中的方法

SecurityContext 接口提供了用於程序安全性的入口點,而且是可注入類型。它有五個方法(都默認爲未實現),如下是方法的列表和用途:

  • Principal getCallerPrincipal(); 若是當前調用者未進行身份驗證,則返回 null,不然返回特定於平臺的主體,代表當前用戶的名稱已經過驗證。
  • Set getPrincipalsByType(Class pType); 從經過身份驗證的調用者的主題中,返回給定類型的全部主體;若是未找到 pType 類型,或者當前用戶未經過身份驗證,則返回一個空集合。
  • boolean isCallerInRole(String role); 肯定指定角色中是否包括調用方;若是未受權,則返回 false。
  • boolean hasAccessToWebResource(String resource, String... methods); 肯定調用方是否能夠經過所提供的方法訪問給定的 web 資源。
  • AuthenticationStatus authenticate(HttpServletRequest req, HttpServletResponse res, AuthenticationParameters param);: 通知容器應該啓動或與調用方繼續以基於 HTTP 身份驗證的方式進行會話。由於依賴於 HttpServletRequestHttpServletResponse 實例,因此此方法僅在 servlet 容器中運行。

咱們將簡要總結使用這些方法的其中之一來檢查用戶對 web 資源的訪問。

使用 SecutiytContext:示例

清單 8 演示瞭如何使用 hasAccessToWebResource() 方法測試調用方對指定 HTTP 方法的給定 web 資源的訪問。在這種狀況下,將 SecurityContext 實例注入到 servlet 中,並在 doGet() 方法中使用,測試調用方 URI /secretServlet 的 servlet 的 GET 方法的訪問。

清單 8. 調用方的 web 資源訪問測試
@DeclareRoles({"admin", "user", "demo"})
@WebServlet("/hasAccessServlet")
public class HasAccessServlet extends HttpServlet {
  
   @Inject
   private SecurityContext securityContext;
   @Override
   public void doGet(HttpServletRequest req, HttpServletResponse res) 
			throws ServletException, IOException {
       boolean hasAccess = securityContext.hasAccessToWebResource("/secretServlet", "GET");
       if (hasAccess) {
           req.getRequestDispatcher("/secretServlet").forward(req, res);
       } else {
           req.getRequestDispatcher("/logout").forward(req, res);
       }
   }
}
複製代碼

第一部分的總結

新的 Java EE Security API 成功地將現有身份驗證和受權機制與開發者指望的現代 Java EE 特性和技術的易用性相結合。

儘管這個 API 的初始目標是尋求以一致性和可移值性的方式解決安全性方面的問題,但仍需繼續改進。在將來的版本中,JSR 375 專家組打算集成用於密碼別名、角色和權限分配以及攔截器受權的 API —— 這些是尚未被歸入規範 v1.0 中的特性。

同時,專家組也但願集成諸如密碼管理與加密等特性,這些特性對於本地雲和微服務應用程序中的常見使用相當重要。此外,2016 Java EE 社區調查還代表 OAuth2 和 OpenID 被選爲 Java EE 8 中包含的第三個重要特性。雖然時間的限制將這些特性排除在 v1.0 中,可是在即將發佈的版本中,包含這些特性確實是有着不可忽視的理由和動機。

您已經對新的 Java EE Security API 的基本特性和組件有了大體的瞭解,我鼓勵您經過下面的快速測試來檢測您所學的內容。下一篇文章將深刻研究 HttpAuthenticationMechanism 接口及其支持的 Servlet 4.0 的三種身份驗證機制。

測試您的理解

  1. 三種默認的 HttpAuthenticationMechanism 實現是什麼?
    1. @BasicFormAuthenticationMechanismDefinition
    2. @FormAuthenticationMechanismDefinition
    3. @LoginFormAuthenticationMechanismDefinition
    4. @CustomFormAuthenticationMechanismDefinition
    5. @BasicAuthenticationMechanismDefinition
  2. 如下哪兩個註解將觸發內置 LDAP 和 RDBMS 標識存儲?
    1. @LdapIdentityStore
    2. @DataBaseIdentityStore
    3. @DataBaseIdentityStoreDefinition
    4. @LdapIdentityStoreDefinition
    5. @RdbmsBaseIdentityStoreDefinition
  3. 如下哪一種說法是正確的?
    1. IdentityStore 只用於 HttpAuthenticationMechanism 的實現。
    2. IdentityStore 可用於任何內置或者定製的安全策略解決方案。
    3. IdentityStore 只能經過注入 IdentityStoreHandler的實現才能夠訪問。
    4. IdentityStore 沒法經過 HttpAuthenticationMechanism 的實現來使用。
  4. SecurityContext 的目標是什麼?
    1. 提供跨 servlet 和 EJB 容器上下文安全訪問的一致性。
    2. 只提供針對 EJB 容器上下文安全訪問的一致性。
    3. 提供對全部容器上下文安全訪問的一致性。
    4. 提供對 Servlet 容器上下文安全訪問的一致性。
    5. 提供跨 EJB 容器對上下文安全訪問的一致性。
  5. 爲何 HttpAuthenticationMechanism 實現必須是 @ApplicationScoped
    1. 爲了確保它是 CDI 管理的 bean,並且能夠供整個應用程序使用。
    2. 爲了讓 HttpAuthenticationMechanism 能夠在全部應用程序級別上使用。
    3. 爲了讓每一個用戶都有一個 HttpAuthenticationMechanism 實例。
    4. JsonAdapter.
    5. 這不是正確的說法。

檢查您的答案

若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索