新的 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 及其三個主要接口:HttpAuthenticationMechanism
、IdentityStore
和 SecurityContext
。git
獲取代碼github
Java EE 安全規範的開發得力於 2014 Java EE 8 問卷調查,社區的反饋推進了 Java EE 安全規範的開發步伐。簡化和標準化 Java 企業級安全是許多調查對象優先考慮的事項。JSR 375專家組一旦成立,將肯定如下問題:web
HttpServletRequest.isUserInRole(String role)
,而 EJB 則調用 EJBContext.isCallerInRole(String roleName)
。這些是 JSR 375 旨在解決的主要問題。同時,該規範經過定義用於身份驗證、身份存儲、角色和權限以及跨容器受權的可移值性 API,促使開發者可以自行管理和控制安全性。sql
Java EE Security API 的優勢在於它提供了一種配置身份存儲和身份驗證機制的替代方法,但並不能取代現有的安全機制。Java EE Security API 容許開發人員以一致的和可移值的方式啓用 Java EE web 應用程序的安全性 —— 不管是否具備特定於供應商的或者獨有的解決方案。數據庫
Java EE Security API 1.0 版本包含了初始提交草案的一個子集,並且側重於本地雲應用程序相關的技術。這些特性是:
這些特性與全部 Java EE 安全實現的新的標準化術語結合在一塊兒。剩餘的特性(計劃包含在下一個版本中)是:
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 容器指定。
Java EE 容器必須爲 Servlet 4.0 規範中定義的三種身份認證機制提供 HttpAuthenticationMechanism
實現。這三種實現是:
每一個實現都由相關注解的存在觸發:
@BasicAuthenticationMechanismDefinition
@FormAuthenticationMechanismDefinition
@CustomFormAuthenticationMechanismDefinition
當遇到這些註解之一時,容器會實例化相關機制的實例,並使其當即可用。
在新規範中,再也不須要像 Servlet 4.0 所要求的那樣,在 web.xml
中的 <login-config>
元素之間指定身份驗證機制。事實上,若是 web.xml
和基於 HttpAuthentication 機制的註解同時存在時,部署過程可能會失敗 —— 至少要忽略 web.xml
配置。
讓咱們看看每種機制的示例是如何運行的。
@BasicAuthenticationMechanismDefinition
註解觸發 Servlet 4.0 定義的基本 HTTP 身份驗證。清單 1 列舉了一個示例。惟一的配置參數是可選的,並且容許指定 realm。
@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
註解傳遞任何參數,由於實現會提供默認值。
@FormAuthenticationMechanismDefinition(
loginToContinue = @LoginToContinue(
loginPage="/login-servlet",
errorPage="/error",
useForwardToLoginExpression="${appConfig.forward}"
)
)
@ApplicationScoped
public class ApplicationConfig { ... }
複製代碼
@CustomFormAuthenticationMechanismDefinition
註解觸發內置自定義表單身份驗證。清單 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 輸入並處理。
標識存儲是存儲用戶標識數據的數據庫,如用戶名、組成員和用於驗證的憑據信息。Java EE Security API 提供了一個名爲 IdentityStore
的抽象標識存儲。相似於 JAAS LoginModule
接口,IdentityStore
用於與標識存儲進行交互,以便對用戶進行身份驗證並檢索組成員身份。
正如規範所描述的,IdentityStore
被 HttpAuthenticationMechanism
的實現所使用,但這不是必須的, IdentityStore
能夠獨立存在,供任何其餘身份驗證機制使用。儘管如此,使用 IdentityStore
和 HttpAuthenticationMechanism
使應用程序可以以可移植和標準化的方式控制用於身份驗證的身份存儲,在大部分用例場景中,都推薦使用。
IdentityStore
API 包括一個 IdentityStoreHandler
接口,HttpAuthenticationMechanism
必須委託它來驗證用戶憑據。以後,IdentityStoreHandler
調用 IdentityStore
實例。Identity
存儲實現不是直接使用的,而是經過專門的處理程序進行交互的。
IdentityStoreHandler
能夠針對多個 IdentityStores
進行身份驗證,而且以 CredentialValidationResult
實例的形式返回聚合結果。不管憑據是否有效,該對象可能只具備傳遞憑據的做用,或者它能夠是包含下述任何信息的豐富對象:
CallerPrincipal
標識存儲按順序進行查詢,這取決於每一個 IdentityStore
實現的優先級。存儲列表被解析了兩次:首先用於身份驗證,而後用於受權。
做爲開發者,您能夠經過實現 IdentityStore
接口來實現本身的輕量級標識存儲,或者您可使用爲 LDAP 和 RDBMS 內置的 IdentityStores
的其中一種。它們是經過將配置細節傳遞給適當的註解來初始化的 —— @LdapIdentityStoreDefinition
或者 @DataBaseIdentityStoreDefinition
。
最簡單的標識存儲是數據庫存儲。它是經過 @DataBaseIdentityStoreDefinition
註解進行配置的。正如清單 4 所演示的那樣,這兩個內置的數據存儲註解基於 Java EE 7 中已有的 @DataStoreDefinition
註解。
清單 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 語義配置方面的經驗,您會發現這裏的選項很是熟悉。
@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
接口,至少要實現 validate()
方法。接口上有四種方法,它們都有默認的實現方式。validate()
方法是運行標識存儲所需的最小條件。它接受 Credential
實例,而後返回 CredentialValidationResults
實例。
在清單 6 中,validate()
方式接收一個包含要驗證的登陸憑據的 UsernamePasswordCredential
實例,而後返回一個 CredentialValidationResults
的實例。若是簡單的配置邏輯促使身份驗證成功,則使用用戶名和用戶所屬組配置該對象。若是身份驗證失敗,那麼 CredentialValidationResults
實例只包含狀態標誌 INVALID
。
@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 演示的那樣。
@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();
}
}
}
複製代碼
IdentityStore
和 HttpAuthenticationMechanism
將用戶的身份驗證和受權完美結合,可是自身的聲明式模型還沒有成型。程序的安全性編碼使 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
接口提供了用於程序安全性的入口點,而且是可注入類型。它有五個方法(都默認爲未實現),如下是方法的列表和用途:
pType
類型,或者當前用戶未經過身份驗證,則返回一個空集合。HttpServletRequest
和 HttpServletResponse
實例,因此此方法僅在 servlet 容器中運行。咱們將簡要總結使用這些方法的其中之一來檢查用戶對 web 資源的訪問。
清單 8 演示瞭如何使用 hasAccessToWebResource()
方法測試調用方對指定 HTTP 方法的給定 web 資源的訪問。在這種狀況下,將 SecurityContext
實例注入到 servlet 中,並在 doGet()
方法中使用,測試調用方 URI /secretServlet
的 servlet 的 GET
方法的訪問。
@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 的三種身份驗證機制。
HttpAuthenticationMechanism
實現是什麼?
@BasicFormAuthenticationMechanismDefinition
@FormAuthenticationMechanismDefinition
@LoginFormAuthenticationMechanismDefinition
@CustomFormAuthenticationMechanismDefinition
@BasicAuthenticationMechanismDefinition
@LdapIdentityStore
@DataBaseIdentityStore
@DataBaseIdentityStoreDefinition
@LdapIdentityStoreDefinition
@RdbmsBaseIdentityStoreDefinition
IdentityStore
只用於 HttpAuthenticationMechanism
的實現。IdentityStore
可用於任何內置或者定製的安全策略解決方案。IdentityStore
只能經過注入 IdentityStoreHandler
的實現才能夠訪問。IdentityStore
沒法經過 HttpAuthenticationMechanism
的實現來使用。SecurityContext
的目標是什麼?
HttpAuthenticationMechanism
實現必須是 @ApplicationScoped
?
HttpAuthenticationMechanism
能夠在全部應用程序級別上使用。HttpAuthenticationMechanism
實例。JsonAdapter
.若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。