使用 Java EE 8 新的註解驅動的 HTTP 身份驗證機制的經典和自定義 Servlet 身份驗證。html
關於本系列:前端
期待已久的 Java EE Security API (JSR 375) 將 Java 企業級安全帶入雲計算和微服務時代的新紀元。本系列的文章將向您展現如何簡化新的安全機制,以及 Java EE 跨容器安全的標準化處理,而後在啓用雲的項目中使用它們。java
本系列的第一篇文章概述了 Java EE Security API (JSR 375),包括對新的高級接口的介紹:HttpAuthenticationMechanism
、IdentityStore
和 SecurityContext
。本文將深刻理解這三部分中的第一部分,您將學習如何在 Java web 示例應用程序中使用 HttpAuthenticationMechanism
來設置並配置用戶身份驗證。android
HttpAuthenticationMechanism
接口是 Java™ EE HTTP 新的身份驗證機制的核心。它擁有三個內置的 CDI(上下文和依賴注入)實現,它們會自動實例化,而後供 CDI 容器調用。這些內置實現支持 Servlet 4.0 指定的三種經典身份驗證方案:基本 HTTP 身份驗證、基於表單的身份驗證和自定義表單身份驗證ios
除了內置的身份驗證方法,您還可使用 HttpAuthenticationMechanism
來開發自定義身份驗證。若是須要支持指定協議和身份驗證令牌,能夠選擇此選項。一些 servlet 容器還能夠提供自定義的 HttpAuthenticationMechanism
實現。git
本文中,您將親自體驗 HttpAuthenticationMechanism
接口及其三個內置實現。我還將向您演示如何編寫自定義 HttpAuthenticationMechanism
身份驗證機制。github
獲取代碼web
咱們將使用 Java EE 8 Security API 指南來實現 Soteria,經過 HttpAuthenticationMechanism
來研究可訪問的內置身份驗證機制和自定義的身份驗證機制。您可使用兩種方法中的一種來獲取 Soteria。後端
在您的 POM 中,使用如下 Maven 座標來指定 Soteria:api
<dependency>
<groupId>org.glassfish.soteria</groupId>
<artifactId>javax.security.enterprise</artifactId>
<version>1.0</version>
</dependency>
複製代碼
符合 Java EE 8 的服務器將擁有本身的新的 Java EE 8 Security API 實現,或者它們依賴於 Sotoria 的實現。沒法如何,你都須要 Java EE 8 的座標。
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>8.0</version>
<scope>provided</scope>
</dependency>
複製代碼
內置的 HTTP 身份驗證機制支持 Servlet 4.0(第 13.6 章節)指定的身份驗證方式。下一章節我將向您演示如何使用註解來啓用三種身份驗證機制,以及如何在 Java web 應用程序中設置和實現每種機制。
@BasicAuthenticationMechanismDefinition
註解觸發 Servlet 4.0(第 13.6.1 章節)定義的 HTTP 基自己份驗證。它有一個可選參數 realmName
,它經過 WWW-Authenticate
報頭指定發送 realm 的名稱。清單 3 演示瞭如何爲名爲 user-realm
的 realm 觸發 HTTP 基自己份驗證。
@BasicAuthenticationMechanismDefinition(realmName="user-realm")
@WebServlet("/user")
@DeclareRoles({ "admin", "user", "demo" })
@ServletSecurity(@HttpConstraint(rolesAllowed = "user"))
public class UserServlet extends HttpServlet { … }
複製代碼
@FormAuthenticationMechanismDefinition
註解引發 Servlet 4.0 規範定義中(第 13.6.3 章節)基於表單的身份驗證。它有一個必須配置的選項。loginToContinue
選項接受配置的 @LoginToContinue
註解,該註解容許應用程序提供 "login to continue" 的功能。您能夠選擇使用合理的默認值或爲此功能指定四個特性中的一個。
在清單 4 中,登陸頁面 URI 被指定爲 /login-servlet
。若是身份驗證失敗,流將傳遞到 /login-servlet-fail
。
@FormAuthenticationMechanismDefinition(
loginToContinue = @LoginToContinue(
loginPage = "/login-servlet",
errorPage = "/login-servlet-fail"
)
)
@ApplicationScoped
public class ApplicationConfig { ... }
複製代碼
要設置跳轉到登陸頁面的方式,請使用 useForwardToLogin
選項。若是須要將此選項設置爲「轉發」或者「重定向」,則應該顯式聲明 true
或者 false
,缺省值爲 true
。或者,您能夠經過傳遞給選項 useForwardToLoginExpression
的 EL 表達式來設置該值。
@LoginToContinue
具備合理的默認值。登陸頁面被設置爲 /login
,同時錯誤頁面被設置爲 /login-error
。
@CustomFormAuthenticationMechanismDefinition
註解爲自定義登陸表單提供了配置選項。在清單 5 中,你能夠發現網站的登陸頁面被標識爲 login.do
。登陸頁面設置爲 @CustomFormAuthenticationMechanismDefinition
註解的loginPage
參數的 loginToContinue
參數的值。注意,loginToContinue
是惟一的參數,並且是可選的。
@CustomFormAuthenticationMechanismDefinition(
loginToContinue = @LoginToContinue(
loginPage="/login.do"
)
)
@WebServlet("/admin")
@DeclareRoles({ "admin", "user", "demo" })
@ServletSecurity(@HttpConstraint(rolesAllowed = "admin"))
public class AdminServlet extends HttpServlet { ... }
複製代碼
清單 6 演示了 login.do
的登陸頁面,它是一個登陸 backing bean 支持的 JSF(JavaServer Pages)頁面,如清單 7 所示。
<form jsf:id="form">
<p>
<strong>Username</strong>
<input jsf:id="username" type="text" jsf:value="#{loginBean.username}" />
</p>
<p>
<strong>Password</strong>
<input jsf:id="password" type="password" jsf:value="#{loginBean.password}" />
</p>
<p>
<input type="submit" value="Login" jsf:action="#{loginBean.login}" />
</p>
</form>
複製代碼
登陸 backing bean 使用 SecurityContext
實例來執行身份驗證,如清單 7 所示。若是驗證成功,將授予用戶對資源的訪問權;不然,流將傳遞給錯誤頁面。在本例中,它將用戶轉發到默認的 URI /login-error
。
@Named
@RequestScoped
public class LoginBean {
@Inject
private SecurityContext securityContext;
@Inject
private FacesContext facesContext;
private String username, password;
public void login() {
Credential credential = new UsernamePasswordCredential(username, new Password(password));
AuthenticationStatus status = securityContext.authenticate(
getRequestFrom(facesContext),
getResponseFrom(facesContext),
withParams().credential(credential));
if (status.equals(SEND_CONTINUE)) {
facesContext.responseComplete();
} else if (status.equals(SEND_FAILURE)) {
addError(facesContext, "Authentication failed");
}
}
// 爲了簡潔而省略一些方法
}
複製代碼
在大多數場景中,您會發現這三個內置的實現已經足以知足您的需求。在某些場景中,您可能更喜歡編寫本身的 HttpAuthenticationMechanism
接口實現。本節中,我將介紹如何編寫自定義的 HttpAuthenticationMechanism
接口。
爲了確保 Java 應用程序可使用它,您須要將 HttpAuthenticationMechanism
接口實現爲具備 @ApplicationScope
的 CDI bean。接口定義瞭如下三種方法:
validateRequest()
身份驗證的 HTTP 請求。secureResponse()
保護 HTTP 相應消息。cleanSubject()
清除提供的主體和憑據的主題。HttpServletRequest
、HttpServletResponse
和 HttpMessageContext
方法都接受相同的參數類型。它們都映射在由容器提供的 JASPIC Server Auth Module 接口所定義的對應方法上。當在 Server Auth
上調用 JASPIC 方法時,它將委託給您自定義的 HttpAuthenticationMechanism
。
@ApplicationScoped
public class CustomAuthenticationMechanism implements HttpAuthenticationMechanism {
@Inject
private IdentityStoreHandler idStoreHandler;
@Override
public AuthenticationStatus validateRequest(HttpServletRequest req,
HttpServletResponse res,
HttpMessageContext msg) {
// use idStoreHandler to authenticate and authorize access
return msg.responseUnauthorized(); // other responses available
}
}
複製代碼
在 HTTP 請求期間,在固定時刻調用 HttpAuthenticationMechanism
實現的方法。圖 1 描述了在 Filter
和 HttpServlet
實例上調用每一個方法的時間。
在執行 doFilter()
或 service()
方法以前調用 validateRequest()
方法,並在 HttpServletResponse
實例上調用 authenticate()
。此方法的目的是容許調用方進行身份驗證。爲了進行這個操做,方法應該擁有調用方 HttpRequest
和 HttpResponse
實例的訪問權限。它可使用這些來獲取請求的身份驗證信息,也能夠爲了調用方重定向到 OAuth 提供者而進行寫入操做。完成身份驗證以後,它可使用 HttpMessageContext
實例來告知身份驗證的狀態。
在執行 doFilter()
或者 service()
以後調用 secureResponse()
方法。它在 servlet 或 過濾器生成的響應上提供後置處理功能。加密是該方法的潛在功能。
在調用 HttpServletRequest
實例上的 logout()
方法以後,調用 cleanSubject()
方法。此方法還可用於刪除註銷時間後與用戶相關的狀態。
HttpMessageContext
接口有一個 HttpAuthenticationMechanism
實例能夠用來與調用它的 ServerAuthModule
進行通訊的方法。
正如我以前說起的那樣,您一般會編寫一個自定義實現來提供內置選項中不可用的功能。一個示例是,在身份驗證流中使用 cookie。
在類的級別中,您可使用可選的 @RememberMe
註解來有效地「記住」用戶身份驗證,並在每一個請求中自動應用它。
@RememberMe(
cookieMaxAgeSeconds = 3600
)
@ApplicationScoped
public class CustomAuthenticationMechanism implements HttpAuthenticationMechanism { … }
複製代碼
這個註解有 8 個配置選項,每個選項都有合理的默認值,所以您沒必要手動實現它們:
cookieMaxAgeSeconds
設置 「remember me」 cookie 的生命週期。cookieMaxAgeSecondsExpression
是 cookieMaxAgeSeconds的 EL 版本。cookieSecureOnly
指定只能經過安全方法(HTTPS)訪問 cookie。cookieSecureOnlyExpression
是 cookieSecureOnly 的 EL 版本。cookieHttpOnly
表示只有 HTTP 請求才能發送 cookie。cookieHttpOnlyExpression
是 cookieHttpOnly 的 EL 版本。cookieName
設置 cookie 的名稱、isRememberMe
"remember me" 的開關。isRememberMeExpression
是 isRememberMe 的 EL 版本。RememberMe
功能被做爲攔截器綁定而實現。容器將攔截對 validateRequest()
和 cleanSubject()
方法的調用。當對包含 RememberMe
cookie 實現的調用,調用 validateRequest()
方法時,它將嘗試對調用方進行身份驗證。若是成功,通知 HttpMessageConext
登陸事件;不然 cookie 將被移出。攔截 cleanSubject()
方法只需刪除 cookie 並完成註銷請求。
新的 HttpAuthenticationMechanism
接口是 Java EE 8 中 web 身份驗證的核心。它內置的三種身份驗證支持 Servlet 4.0 中指定的經典身份驗證方法,並且也很容易爲自定義實現進行接口擴展。在本教程中,您學習瞭如何使用註解來調用和配置 HttpAuthenticationMechanism
的內置機制,以及如何爲特殊用例編寫自定義機制。我鼓勵您用下面的小測驗來測試您所學到的東西。
這篇文章深刻地介紹新的 Java EE 8 Security API 的三個主要組件中的第一個。接下來的兩篇文章將介紹 IdentityStore
和 SecurityContext
API 的實踐。
HttpAuthenticationMechanism
實現是什麼?
@BasicFormAuthenticationMechanismDefinition
@FormAuthenticationMechanismDefinition
@LoginFormAuthenticationMechanismDefinition
@CustomFormAuthenticationMechanismDefinition
@BasicAuthenticationMechanismDefinition
@BasicAuthenticationMechanismDefinition
@BasicFormAuthenticationMechanismDefinition
@FormAuthenticationMechanismDefinition
@FormBasedAuthenticationMechanismDefinition
@CustomFormAuthenticationMechanismDefinition
@BasicAuthenticationMechanismDefinition(realmName="user-realm")
@BasicAuthenticationMechanismDefinition(userRealm="user-realm")
@BasicAuthenticationMechanismDefinition(loginToContinue = @LoginToContinue)
@BasicAuthenticationMechanismDefinition
@BasicAuthenticationMechanismDefinition(realm="user-realm")
@FormAuthenticationMechanismDefinition(loginToContinue = @LoginToContinue)
@FormAuthenticationMechanismDefinition
@FormBasedAuthenticationMechanismDefinition
@FormAuthenticationMechanismDefinition(loginToContinue = @LoginToContinue(useForwardToLoginExpression = "${appConfigs.forward}"))
@FormBasedAuthenticationMechanismDefinition(loginToContinue = @LoginToContinue)
HttpAuthenticationMechanism
、Filter
和 HttpServlet
實現上調用方法?
doFilter()
, validateRequest()
, service()
, secureResponse()
validateRequest()
, doFilter()
, secureResponse()
, service()
validateRequest()
, service()
, doFilter()
, secureResponse()
validateRequest()
, doFilter()
, service()
, secureResponse()
service()
, secureResponse()
, doFilter()
, validateRequest()
RememberMe
cookie 設置最長有效時間?
@RememberMe(cookieMaxAge = (units = SECONDS, value = 3600)
@RememberMe(maxAgeSeconds = 3600)
@RememberMe(cookieMaxAgeSeconds = 3600)
@RememberMe(cookieMaxAgeMilliseconds = 3600000)
@RememberMe(cookieMaxAgeSeconds = "3600")
若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。