[译] 从 Java EE 8 Security API 开始 —— 第二部分

从 Java EE 8 Security API 开始 —— 第二部分

基于 HttpAuthenticationMechanism 认证

使用 Java EE 8 新的注解驱动的 HTTP 身份验证机制的经典和自定义 Servlet 身份验证。

关于本系列:

期待已久的 Java EE Security API (JSR 375) 将 Java 企业级安全带入云计算和微服务时代的新纪元。本系列的文章将向您展示如何简化新的安全机制,以及 Java EE 跨容器安全的标准化处理,然后在启用云的项目中使用它们。

本系列的第一篇文章概述了 Java EE Security API (JSR 375),包括对新的高级接口的介绍:HttpAuthenticationMechanismIdentityStoreSecurityContext。本文将深入理解这三部分中的第一部分,您将学习如何在 Java web 示例应用程序中使用 HttpAuthenticationMechanism 来设置并配置用户身份验证。

HttpAuthenticationMechanism 接口是 Java™ EE HTTP 新的身份验证机制的核心。它拥有三个内置的 CDI(上下文和依赖注入)实现,它们会自动实例化,然后供 CDI 容器调用。这些内置实现支持 Servlet 4.0 指定的三种经典身份验证方案:基本 HTTP 身份验证、基于表单的身份验证和自定义表单身份验证

除了内置的身份验证方法,您还可以使用 HttpAuthenticationMechanism 来开发自定义身份验证。如果需要支持指定协议和身份验证令牌,可以选择此选项。一些 servlet 容器还可以提供自定义的 HttpAuthenticationMechanism 实现。

本文中,您将亲自体验 HttpAuthenticationMechanism 接口及其三个内置实现。我还将向您演示如何编写自定义 HttpAuthenticationMechanism 身份验证机制。

获取代码

安装 Soteria

我们将使用 Java EE 8 Security API 指南来实现 Soteria,通过 HttpAuthenticationMechanism 来研究可访问的内置身份验证机制和自定义的身份验证机制。您可以使用两种方法中的一种来获取 Soteria。

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

在您的 POM 中,使用以下 Maven 坐标来指定 Soteria:

清单 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.@BasicFormAuthenticationMechanismDefinition2.@FormAuthenticationMechanismDefinition3.@LoginFormAuthenticationMechanismDefinition4.@CustomFormAuthenticationMechanismDefinition5.@BasicAuthenticationMechanismDefinition
2.一下哪两个注释会引发基于表单的身份验证?1.@BasicAuthenticationMechanismDefinition2.@BasicFormAuthenticationMechanismDefinition3.@FormAuthenticationMechanismDefinition4.@FormBasedAuthenticationMechanismDefinition5.@CustomFormAuthenticationMechanismDefinition
3.下列哪两项是基于身份验证的有效配置?1.@BasicAuthenticationMechanismDefinition(realmName="user-realm")2.@BasicAuthenticationMechanismDefinition(userRealm="user-realm")3.@BasicAuthenticationMechanismDefinition(loginToContinue = @LoginToContinue)4.@BasicAuthenticationMechanismDefinition5.@BasicAuthenticationMechanismDefinition(realm="user-realm")
4.下列哪三项是基于表单的身份验证的有效配置?1.@FormAuthenticationMechanismDefinition(loginToContinue = @LoginToContinue)2.@FormAuthenticationMechanismDefinition3.@FormBasedAuthenticationMechanismDefinition4.@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前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值