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

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

基於 HttpAuthenticationMechanism 認證

使用 Java EE 8 新的註解驅動的 HTTP 身份驗證機制的經典和自定義 Servlet 身份驗證。html

關於本系列:前端

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

本系列的第一篇文章概述了 Java EE Security API (JSR 375),包括對新的高級接口的介紹:HttpAuthenticationMechanismIdentityStoreSecurityContext。本文將深刻理解這三部分中的第一部分,您將學習如何在 Java web 示例應用程序中使用 HttpAuthenticationMechanism 來設置並配置用戶身份驗證。android

HttpAuthenticationMechanism 接口是 Java™ EE HTTP 新的身份驗證機制的核心。它擁有三個內置的 CDI(上下文和依賴注入)實現,它們會自動實例化,而後供 CDI 容器調用。這些內置實現支持 Servlet 4.0 指定的三種經典身份驗證方案:基本 HTTP 身份驗證、基於表單的身份驗證和自定義表單身份驗證ios

除了內置的身份驗證方法,您還可使用 HttpAuthenticationMechanism 來開發自定義身份驗證。若是須要支持指定協議和身份驗證令牌,能夠選擇此選項。一些 servlet 容器還能夠提供自定義的 HttpAuthenticationMechanism 實現。git

本文中,您將親自體驗 HttpAuthenticationMechanism 接口及其三個內置實現。我還將向您演示如何編寫自定義 HttpAuthenticationMechanism 身份驗證機制。github

獲取代碼web

安裝 Soteria

咱們將使用 Java EE 8 Security API 指南來實現 Soteria,經過 HttpAuthenticationMechanism 來研究可訪問的內置身份驗證機制和自定義的身份驗證機制。您可使用兩種方法中的一種來獲取 Soteria。後端

1. 在您的 POM 中,顯式指定 Soteria

在您的 POM 中,使用如下 Maven 座標來指定 Soteria:api

清單 1. Soteria 項目的 Maven 座標
<dependency>
  <groupId>org.glassfish.soteria</groupId>
  <artifactId>javax.security.enterprise</artifactId>
  <version>1.0</version>
</dependency>
複製代碼

2. 使用內置的 Java EE 8 座標

符合 Java EE 8 的服務器將擁有本身的新的 Java EE 8 Security API 實現,或者它們依賴於 Sotoria 的實現。沒法如何,你都須要 Java EE 8 的座標。

清單 2. Java EE 8 的 Maven 座標
<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

@BasicAuthenticationMechanismDefinition 註解觸發 Servlet 4.0(第 13.6.1 章節)定義的 HTTP 基自己份驗證。它有一個可選參數 realmName,它經過 WWW-Authenticate 報頭指定發送 realm 的名稱。清單 3 演示瞭如何爲名爲 user-realm 的 realm 觸發 HTTP 基自己份驗證。

清單 3. HTTP 基自己份驗證機制
@BasicAuthenticationMechanismDefinition(realmName="user-realm")
@WebServlet("/user")
@DeclareRoles({ "admin", "user", "demo" })
@ServletSecurity(@HttpConstraint(rolesAllowed = "user"))
public class UserServlet extends HttpServlet { … }
複製代碼

@FormAuthenticationMechanismDefinition

@FormAuthenticationMechanismDefinition 註解引發 Servlet 4.0 規範定義中(第 13.6.3 章節)基於表單的身份驗證。它有一個必須配置的選項。loginToContinue 選項接受配置的 @LoginToContinue 註解,該註解容許應用程序提供 "login to continue" 的功能。您能夠選擇使用合理的默認值或爲此功能指定四個特性中的一個。

在清單 4 中,登陸頁面 URI 被指定爲 /login-servlet。若是身份驗證失敗,流將傳遞到 /login-servlet-fail

清單 4. 基於表單的身份驗證機制
@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

@CustomFormAuthenticationMechanismDefinition 註解爲自定義登陸表單提供了配置選項。在清單 5 中,你能夠發現網站的登陸頁面被標識爲 login.do。登陸頁面設置爲 @CustomFormAuthenticationMechanismDefinition 註解的loginPage 參數的 loginToContinue 參數的值。注意,loginToContinue 是惟一的參數,並且是可選的。

清單 5. 自定義表單配置
@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 所示。

清單 6. login.do JSF 登陸頁面
<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

清單 7. 登陸 backing bean
@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 接口實現。本節中,我將介紹如何編寫自定義的 HttpAuthenticationMechanism 接口。

爲了確保 Java 應用程序可使用它,您須要將 HttpAuthenticationMechanism 接口實現爲具備 @ApplicationScope 的 CDI bean。接口定義瞭如下三種方法:

  • validateRequest() 身份驗證的 HTTP 請求。
  • secureResponse() 保護 HTTP 相應消息。
  • cleanSubject() 清除提供的主體和憑據的主題。

HttpServletRequestHttpServletResponseHttpMessageContext 方法都接受相同的參數類型。它們都映射在由容器提供的 JASPIC Server Auth Module 接口所定義的對應方法上。當在 Server Auth 上調用 JASPIC 方法時,它將委託給您自定義的 HttpAuthenticationMechanism

清單 8. 自定義 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 請求期間執行方法

在 HTTP 請求期間,在固定時刻調用 HttpAuthenticationMechanism 實現的方法。圖 1 描述了在 FilterHttpServlet 實例上調用每一個方法的時間。

圖 1. 方法調用順序

方法調用順序

在執行 doFilter()service() 方法以前調用 validateRequest() 方法,並在 HttpServletResponse 實例上調用 authenticate()。此方法的目的是容許調用方進行身份驗證。爲了進行這個操做,方法應該擁有調用方 HttpRequestHttpResponse 實例的訪問權限。它可使用這些來獲取請求的身份驗證信息,也能夠爲了調用方重定向到 OAuth 提供者而進行寫入操做。完成身份驗證以後,它可使用 HttpMessageContext 實例來告知身份驗證的狀態。

在執行 doFilter() 或者 service() 以後調用 secureResponse() 方法。它在 servlet 或 過濾器生成的響應上提供後置處理功能。加密是該方法的潛在功能。

在調用 HttpServletRequest 實例上的 logout() 方法以後,調用 cleanSubject() 方法。此方法還可用於刪除註銷時間後與用戶相關的狀態。

HttpMessageContext 接口有一個 HttpAuthenticationMechanism 實例能夠用來與調用它的 ServerAuthModule 進行通訊的方法。

自定義示例:使用 cookie 進行身份驗證

正如我以前說起的那樣,您一般會編寫一個自定義實現來提供內置選項中不可用的功能。一個示例是,在身份驗證流中使用 cookie。

在類的級別中,您可使用可選的 @RememberMe 註解來有效地「記住」用戶身份驗證,並在每一個請求中自動應用它。

清單 9. 在自定義的 HttpAuthenticationMechanism 中使用 @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 的三個主要組件中的第一個。接下來的兩篇文章將介紹 IdentityStoreSecurityContext API 的實踐。

測試您的掌握程度

  1. 三種默認的 HttpAuthenticationMechanism 實現是什麼?
    1. @BasicFormAuthenticationMechanismDefinition
    2. @FormAuthenticationMechanismDefinition
    3. @LoginFormAuthenticationMechanismDefinition
    4. @CustomFormAuthenticationMechanismDefinition
    5. @BasicAuthenticationMechanismDefinition
  2. 一下哪兩個註釋會引起基於表單的身份驗證?
    1. @BasicAuthenticationMechanismDefinition
    2. @BasicFormAuthenticationMechanismDefinition
    3. @FormAuthenticationMechanismDefinition
    4. @FormBasedAuthenticationMechanismDefinition
    5. @CustomFormAuthenticationMechanismDefinition
  3. 下列哪兩項是基於身份驗證的有效配置?
    1. @BasicAuthenticationMechanismDefinition(realmName="user-realm")
    2. @BasicAuthenticationMechanismDefinition(userRealm="user-realm")
    3. @BasicAuthenticationMechanismDefinition(loginToContinue = @LoginToContinue)
    4. @BasicAuthenticationMechanismDefinition
    5. @BasicAuthenticationMechanismDefinition(realm="user-realm")
  4. 下列哪三項是基於表單的身份驗證的有效配置?
    1. @FormAuthenticationMechanismDefinition(loginToContinue = @LoginToContinue)
    2. @FormAuthenticationMechanismDefinition
    3. @FormBasedAuthenticationMechanismDefinition
    4. @FormAuthenticationMechanismDefinition(loginToContinue = @LoginToContinue(useForwardToLoginExpression = "${appConfigs.forward}"))
    5. @FormBasedAuthenticationMechanismDefinition(loginToContinue = @LoginToContinue)
  5. 在 HTTP 請求期間,按照什麼順序,在 HttpAuthenticationMechanismFilterHttpServlet 實現上調用方法?
    1. doFilter(), validateRequest(), service(), secureResponse()
    2. validateRequest(), doFilter(), secureResponse(), service()
    3. validateRequest(), service(), doFilter(), secureResponse()
    4. validateRequest(), doFilter(), service(), secureResponse()
    5. service(), secureResponse(), doFilter(), validateRequest()
  6. 如何爲 RememberMe cookie 設置最長有效時間?
    1. @RememberMe(cookieMaxAge = (units = SECONDS, value = 3600)
    2. @RememberMe(maxAgeSeconds = 3600)
    3. @RememberMe(cookieMaxAgeSeconds = 3600)
    4. @RememberMe(cookieMaxAgeMilliseconds = 3600000)
    5. @RememberMe(cookieMaxAgeSeconds = "3600")

檢查您的答案

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


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

相關文章
相關標籤/搜索