容器和客户端拦截器
7.1 关于集装箱拦截器
预期在容器已完成安全上下文传播,事务管理和其他容器提供的调用处理之后,将运行由JSR 345,Enterprise JavaBeans 3.2规范定义的标准Java EE拦截器。如果应用程序在运行特定的容器拦截器之前必须拦截一个调用,这是一个问题。
拦截器链中容器拦截器的定位
为EJB配置的容器拦截器保证在JBoss EAP提供安全拦截器,事务管理拦截器和其他服务器提供的拦截器之前运行。这允许特定的应用程序容器拦截器在调用进行之前处理或配置相关的上下文数据。
容器拦截器和Java EE拦截器API之间的区别
虽然容器拦截器被建模为类似于JavaEE拦截器,但API的语义有一些差异。例如,容器拦截器调用javax.interceptor.InvocationContext.getTarget()方法是非法的,因为在EJB组件设置或实例化之前很长时间调用这些拦截器。
7.2 创建集装箱拦截器类
容器拦截器类是简单的普通Java对象(POJO)。他们使用@ javax.annotation.AroundInvoke标记在bean调用期间调用的方法。
以下是容器拦截器类的示例,该类标记用于调用的iAmAround方法:
容器拦截器代码示例:
有关如何配置jboss-ejb3.xml描述符文件以使用容器拦截器类的示例,请参阅配置容器拦截器。
public class ClassLevelContainerInterceptor {
@AroundInvoke
private Object iAmAround(final InvocationContext invocationContext) throws Exception {
return this.getClass().getName() + " " + invocationContext.proceed();
}
}
有关如何配置jboss-ejb3.xml描述符文件以使用容器拦截器类的示例,请参阅配置容器拦截器。
7.3 配置集装箱接口
容器拦截器使用标准的Java EE拦截器库,这意味着它们使用ejb-jar.xml文件中允许的3.2版本的ejb-jar部署描述符中相同的XSD元素。因为它们基于标准的Java EE拦截库,所以容器拦截器只能使用部署描述符进行配置。这是通过设计完成的,因此应用程序不需要任何JBoss特定注释或其他库依赖关系。有关容器拦截器的更多信息,请参阅关于容器拦截器。
以下过程介绍如何配置容器拦截器。
1.在EJB部署的META-INF目录中创建一个jboss-ejb3.xml文件。
2.在描述符文件中配置容器拦截器元素。
a.使用urn:container-interceptors:1.0命名空间指定容器拦截器元素的配置。
b.使用<container-interceptors>元素指定容器拦截器。
c.使用<interceptor-binding>元素将容器拦截器绑定到EJB。拦截器可以以下列任何一种方式进行绑定:
*使用*通配符将拦截器绑定到部署中的所有EJB。
*使用特定的EJB名称在各个bean级别绑定拦截器。
*在EJB的特定方法级别绑定拦截器。
注意
这些元素使用EJB 3.2 XSD以与Java EE拦截器相同的方式进行配置。
3.查看以下描述符文件以获取上述元素的示例。
容器拦截器jboss-ejb3.xml文件示例:<jboss xmlns="http://www.jboss.com/xml/ns/javaee"
xmlns:jee="http://java.sun.com/xml/ns/javaee"
xmlns:ci ="urn:container-interceptors:1.0">
<jee:assembly-descriptor>
<ci:container-interceptors>
<!-- Default interceptor -->
<jee:interceptor-binding>
<ejb-name>*</ejb-name>
<interceptor-class>org.jboss.as.test.integration.ejb.container.interceptor.ContainerInterceptorOne</interceptor-class>
</jee:interceptor-binding>
<!-- Class level container-interceptor -->
<jee:interceptor-binding>
<ejb-name>AnotherFlowTrackingBean</ejb-name>
<interceptor-class>org.jboss.as.test.integration.ejb.container.interceptor.ClassLevelContainerInterceptor</interceptor-class>
</jee:interceptor-binding>
<!-- Method specific container-interceptor -->
<jee:interceptor-binding>
<ejb-name>AnotherFlowTrackingBean</ejb-name>
<interceptor-class>org.jboss.as.test.integration.ejb.container.interceptor.MethodSpecificContainerInterceptor</interceptor-class>
<method>
<method-name>echoWithMethodSpecificContainerInterceptor</method-name>
</method>
</jee:interceptor-binding>
<!-- container interceptors in a specific order -->
<jee:interceptor-binding>
<ejb-name>AnotherFlowTrackingBean</ejb-name>
<interceptor-order>
<interceptor-class>org.jboss.as.test.integration.ejb.container.interceptor.ClassLevelContainerInterceptor</interceptor-class>
<interceptor-class>org.jboss.as.test.integration.ejb.container.interceptor.MethodSpecificContainerInterceptor</interceptor-class>
<interceptor-class>org.jboss.as.test.integration.ejb.container.interceptor.ContainerInterceptorOne</interceptor-class>
</interceptor-order>
<method>
<method-name>echoInSpecificOrderOfContainerInterceptors</method-name>
</method>
</jee:interceptor-binding>
</ci:container-interceptors>
</jee:assembly-descriptor>
</jboss>
urn:container-interceptors:1.0命名空间的模式可从http://www.jboss.org/schema/jbossas/jboss-ejb-container-interceptors_1_0.xsd获得。
7.4 更改安全性背景知识
默认情况下,当您对部署到应用程序服务器的EJB进行远程调用时,将对服务器的连接进行身份验证,并使用原始身份验证身份执行任何使用连接的后续请求。对于客户端到服务器和服务器到服务器的呼叫都是如此。如果您需要使用来自同一客户端的不同身份,通常您必须打开到服务器的多个连接,以便每个连接被认证为不同的身份。不必打开多个客户端连接,您可以授权经过身份验证的用户切换身份,并以不同的用户身份在现有连接上执行请求。
在服务器端创建和配置的拦截器称为容器拦截器。在客户端创建和配置的拦截器称为客户端拦截器。要更改安全连接的身份,您必须创建并配置以下三个组件。
*客户端拦截器
*集装箱拦截器
*JAASLoginModule
随后的简化代码示例来自JBossEAP附带的ejb安全拦截器快速入门。此快速启动是一个简单的Maven项目,它提供了如何在现有连接上切换身份的工作示例。
创建和配置客户端拦截器
1.创建客户端拦截器。
客户机拦截器必须实现org.jboss.ejb.client.EJBClientInterceptor接口。拦截器必须通过上下文数据映射传递请求的身份,这可以通过使用对EJBClientInvocationContext.getContextData()的调用来获取。以下是交换身份的客户端拦截器的示例。
客户端拦截器代码示例:
public class ClientSecurityInterceptor implements EJBClientInterceptor {
public void handleInvocation(EJBClientInvocationContext context) throws Exception {
Principal currentPrincipal = SecurityActions.securityContextGetPrincipal();
if (currentPrincipal != null) {
Map<String, Object> contextData = context.getContextData();
contextData.put(ServerSecurityInterceptor.DELEGATED_USER_KEY, currentPrincipal.getName());
}
context.sendRequest();
}
public Object handleInvocationResult(EJBClientInvocationContext context) throws Exception {
return context.getResult();
}
}
2.配置客户端拦截器。
应用程序可以以编程方式或通过使用服务加载程序机制将客户端拦截器插入到EJBClientContext拦截链中。 有关配置客户端拦截器的说明,请参阅在应用程序中使用客户端拦截器。
创建和配置容器拦截器
容器拦截器类是简单的普通Java对象(POJO)。 他们使用@ javax.annotation.AroundInvoke标记在bean调用期间应该调用的方法。 有关容器拦截器的更多信息,请参阅关于容器拦截器。
1.创建容器拦截器。
该拦截器收到包含该身份的InvocationContext,并使请求切换到该新标识。以下是实际代码示例的简短版本:
容器拦截器代码示例:public class ServerSecurityInterceptor {
private static final Logger logger = Logger.getLogger(ServerSecurityInterceptor.class);
static final String DELEGATED_USER_KEY = ServerSecurityInterceptor.class.getName() + ".DelegationUser";
@AroundInvoke
public Object aroundInvoke(final InvocationContext invocationContext) throws Exception {
Principal desiredUser = null;
UserPrincipal connectionUser = null;
Map<String, Object> contextData = invocationContext.getContextData();
if (contextData.containsKey(DELEGATED_USER_KEY)) {
desiredUser = new SimplePrincipal((String) contextData.get(DELEGATED_USER_KEY));
Collection<Principal> connectionPrincipals = SecurityActions.getConnectionPrincipals();
if (connectionPrincipals != null) {
for (Principal current : connectionPrincipals) {
if (current instanceof UserPrincipal) {
connectionUser = (UserPrincipal) current;
break;
}
}
} else {
throw new IllegalStateException("Delegation user requested but no user on connection found.");
}
}
ContextStateCache stateCache = null;
try {
if (desiredUser != null && connectionUser != null
&& (desiredUser.getName().equals(connectionUser.getName()) == false)) {
// The final part of this check is to verify that the change does actually indicate a change in user.
try {
// We have been requested to use an authentication token
// so now we attempt the switch.
stateCache = SecurityActions.pushIdentity(desiredUser, new OuterUserCredential(connectionUser));
} catch (Exception e) {
logger.error("Failed to switch security context for user", e);
// Don't propagate the exception stacktrace back to the client for security reasons
throw new EJBAccessException("Unable to attempt switching of user.");
}
}
return invocationContext.proceed();
} finally {
// switch back to original context
if (stateCache != null) {
SecurityActions.popIdentity(stateCache);;
}
}
}
2.配置容器拦截器。
有关如何配置容器拦截器的信息,请参阅配置容器拦截器。
创建JAASLoginModule
JAAS LoginModule组件负责验证用户是否被允许以请求的身份执行请求。以下简化代码示例显示执行登录和验证的方法:
LoginModule代码示例:@SuppressWarnings("unchecked")
@Override
public boolean login() throws LoginException {
if (super.login() == true) {
log.debug("super.login()==true");
return true;
}
// Time to see if this is a delegation request.
NameCallback ncb = new NameCallback("Username:");
ObjectCallback ocb = new ObjectCallback("Password:");
try {
callbackHandler.handle(new Callback[] { ncb, ocb });
} catch (Exception e) {
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
// If the CallbackHandler can not handle the required callbacks then no chance.
return false;
}
String name = ncb.getName();
Object credential = ocb.getCredential();
if (credential instanceof OuterUserCredential) {
// This credential type will only be seen for a delegation request, if not seen then the request is not for us.
if (delegationAcceptable(name, (OuterUserCredential) credential)) {
identity = new SimplePrincipal(name);
if (getUseFirstPass()) {
String userName = identity.getName();
if (log.isDebugEnabled())
log.debug("Storing username '" + userName + "' and empty password");
// Add the username and an empty password to the shared state map
sharedState.put("javax.security.auth.login.name", identity);
sharedState.put("javax.security.auth.login.password", "");
}
loginOk = true;
return true;
}
}
return false; // Attempted login but not successful.
}
// Make a trust user to decide if the user switch is acceptable.
protected boolean delegationAcceptable(String requestedUser, OuterUserCredential connectionUser) {
if (delegationMappings == null) {
return false;
}
String[] allowedMappings = loadPropertyValue(connectionUser.getName(), connectionUser.getRealm());
if (allowedMappings.length == 1 && "*".equals(allowedMappings[0])) {
// A wild card mapping was found.
return true;
}
for (String current : allowedMappings) {
if (requestedUser.equals(current)) {
return true;
}
}
return false;
}
注意
有关代码示例的详细信息,请参阅ejb-security-interceptors快速入门README.html文件。
7.5 在应用程序中使用客户接口
应用程序可以以编程方式或通过使用服务加载程序机制将客户机拦截器插入到EJBClientContext拦截器链中。
以编程方式插入拦截器
调用org.jboss.ejb.client.EJBClientContext.registerInterceptor(int order,EJBClientInterceptor interceptor)方法并传递顺序和拦截器实例。该顺序确定此客户机拦截器在拦截器链中的位置。
使用服务加载程序机制插入拦截器
创建META-INF /services / org.jboss.ejb.client.EJBClientInterceptor文件,并将其放置或打包到客户端应用程序的类路径中。该文件的规则由Java ServiceLoader机制决定。
*该文件预计将为EJB客户机拦截器实现的每个完全限定类名包含一个单独的行。
*EJB客户机拦截器类必须在类路径中可用。
使用服务加载器机制添加的EJB客户端拦截器按照它们在类路径中找到的顺序添加,并添加到客户机拦截器链的末尾。 Red Hat JBoss Enterprise Application Platform附带的ejb安全拦截器快速启动使用这种方法。
原文地址: