使用 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、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。