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