在 JBoss 7.4 中,Jaspic 模块未将主体传播到本地 EJB
Jaspic module not propagating principal to local EJB in JBoss 7.4
我有一个自定义的 JSR-196 模块,它基本上委托给一个服务,该服务将角色委托给 OAuth "grants" 调用。
它在 servlet 中工作(request.getUserPrincipal() 工作正常)。
它不会传播到 EJB 调用,其中 SessionContext.getCallerPrincipal() returns 具有 "anonymous" 而不是预期的用户名/角色的 SimplePrincipal。
MycompanyPrincipal 是一个简单的 class,具有简单的 getName() 和一些自定义属性。
好像SubjectInfo.getAuthenticatedSubject()没有本金
我设法为此做了一个丑陋的解决方法,请参阅下面的“// WORKAROUND”。
不过,我还是想以正确的方式去做(如果可能的话,甚至 standard/portable)。
这是我在 standalone.xml 中定义我的安全域的地方:
<security-domain name="mycompany" cache-type="default">
<authentication-jaspi>
<login-module-stack name="lm-stack">
<login-module code="UsersRoles" flag="required">
<module-option name="usersProperties" value="../standalone/configuration/jaspi-users.properties"/>
<module-option name="rolesProperties" value="../standalone/configuration/jaspi-roles.properties"/>
</login-module>
</login-module-stack>
<auth-module code="be.mycompany.api.authentication.jaspi.MycompanyAuthModule" flag="required" login-module-stack-ref="lm-stack"/>
</authentication-jaspi>
</security-domain>
这是我的 jboss-web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<jboss-web>
<context-root>api/rules/dev</context-root>
<security-domain>mycompany</security-domain>
<valve>
<class-name>org.jboss.as.web.security.jaspi.WebJASPIAuthenticator</class-name>
</valve>
</jboss-web>
模块本身是我的应用程序的一部分(我的 war 中的一个 jar)。 EJB 在其他 JAR 中定义,它们也以 WEB-INF/lib.
结尾
serviceSubject.getPrincipals().add(degroofPrincipal)
这是我的模块(将 ejb 调用更改为静态方法调用):
package be.mycompany.api.authentication.jaspi;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.message.AuthException;
import javax.security.auth.message.AuthStatus;
import javax.security.auth.message.MessageInfo;
import javax.security.auth.message.MessagePolicy;
import javax.security.auth.message.callback.CallerPrincipalCallback;
import javax.security.auth.message.callback.GroupPrincipalCallback;
import javax.security.auth.message.config.ServerAuthContext;
import javax.security.auth.message.module.ServerAuthModule;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.HttpHeaders;
import org.jboss.security.SecurityContext;
import org.jboss.security.SecurityContextAssociation;
import org.jboss.security.SubjectInfo;
/**
*
* @author devyam
*/
public class MycompanyAuthModule implements ServerAuthModule, ServerAuthContext {
private static final String BEARER_PREFIX = "bearer ";
private CallbackHandler handler;
private final Class<?>[] supportedMessageTypes = new Class[]{HttpServletRequest.class, HttpServletResponse.class};
protected String delegatingLoginContextName = null;
// private MycompanyAuthenticationService mycompanyAuthenticationService;
/**
* <p>
* Creates an instance of {@code HTTPBasicServerAuthModule}.
* </p>
*/
public MycompanyAuthModule() {
// lookupMycompanyAuthenticationService();
}
/**
* <p>
* Creates an instance of {@code HTTPBasicServerAuthModule} with the
* specified delegating login context name.
* </p>
*
* @param delegatingLoginContextName the name of the login context
* configuration that contains the JAAS modules that are to be called by
* this module.
*/
public MycompanyAuthModule(String delegatingLoginContextName) {
this();
this.delegatingLoginContextName = delegatingLoginContextName;
}
@Override
public void initialize(MessagePolicy requestPolicy,
MessagePolicy responsePolicy, CallbackHandler handler,
@SuppressWarnings("rawtypes") Map options) throws AuthException {
this.handler = handler;
}
/**
* WebLogic 12c calls this before Servlet is called, Geronimo v3 after,
* JBoss EAP 6 and GlassFish 3.1.2.2 don't call this at all. WebLogic
* (seemingly) only continues if SEND_SUCCESS is returned, Geronimo
* completely ignores return value.
*/
@Override
public AuthStatus secureResponse(MessageInfo messageInfo, Subject serviceSubject) throws AuthException {
return AuthStatus.SEND_SUCCESS;
}
@Override
public AuthStatus validateRequest(MessageInfo messageInfo, Subject clientSubject, Subject serviceSubject) throws AuthException {
HttpServletRequest request = (HttpServletRequest) messageInfo.getRequestMessage();
HttpServletResponse response = (HttpServletResponse) messageInfo.getResponseMessage();
String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
if (authHeader != null && authHeader.startsWith(BEARER_PREFIX)) {
String token = authHeader.substring(BEARER_PREFIX.length());
MycompanyPrincipal mycompanyPrincipal = MycompanyAuthenticationService.createPrincipal(token);
List<String> groups = MycompanyAuthenticationService.getGroups(mycompanyPrincipal);
String[] groupArray = groups.toArray(new String[0]);
CallerPrincipalCallback callerPrincipalCallback = new CallerPrincipalCallback(clientSubject, mycompanyPrincipal);
GroupPrincipalCallback groupPrincipalCallback = new GroupPrincipalCallback(clientSubject, groupArray);
try {
handler.handle(new Callback[]{callerPrincipalCallback, groupPrincipalCallback});
} catch (IOException | UnsupportedCallbackException exception) {
throw new RuntimeException(exception);
}
//////// WORKAROUND: doesn't work without this in EJBs!
SecurityContext oldContext = SecurityContextAssociation.getSecurityContext();
SubjectInfo subjectInfo = oldContext.getSubjectInfo();
subjectInfo.setAuthenticatedSubject(serviceSubject);
SecurityContextAssociation.setPrincipal(mycompanyPrincipal);
serviceSubject.getPrincipals().add(mycompanyPrincipal);
////////////// end of workaround
return AuthStatus.SUCCESS;
}
response.setStatus(401);
return AuthStatus.FAILURE;
}
/**
* A compliant implementation should return HttpServletRequest and
* HttpServletResponse, so the delegation class {@link ServerAuthContext}
* can choose the right SAM to delegate to. In this example there is only
* one SAM and thus the return value actually doesn't matter here.
*/
@Override
public Class<?>[] getSupportedMessageTypes() {
return supportedMessageTypes;
}
@Override
public void cleanSubject(MessageInfo messageInfo, Subject subject)
throws AuthException {
}
// private void lookupMycompanyAuthenticationService() throws RuntimeException {
// try {
// BeanManager beanManager = InitialContext.doLookup("java:comp/BeanManager");
// Bean<?> mycompanyAuthenticationServiceBean = beanManager.getBeans(MycompanyAuthenticationService.class).iterator().next();
// CreationalContext creationalContext = beanManager.createCreationalContext(mycompanyAuthenticationServiceBean);
// mycompanyAuthenticationService = (MycompanyAuthenticationService) beanManager.getReference(mycompanyAuthenticationServiceBean, MycompanyAuthenticationService.class, creationalContext);
// } catch (NamingException exception) {
// throw new RuntimeException(exception);
// }
// }
}
不幸的是,尽管 JBoss 工程师尽了最大努力,但将经过身份验证的身份从 Servlet 传播到 EJB 对 JBoss 来说是一个永无止境的故事。
有大约 6 个单独的错误需要修复,因此您甚至可以到达现在的 JBoss AS 7.4(JBoss EAP 6.3 我假设),并且在此之后有几个错误。
这个特定的错误是 https://issues.jboss.org/browse/SECURITY-745 and was filed almost 2 years ago, but still open for the AS 7/EAP 6 branch. This one came right after https://issues.jboss.org/browse/SECURITY-744,它被列为开放,但我认为它实际上已修复。
WF 8/EAP 7 分支没有这个错误,但是两个分支都有 https://issues.jboss.org/browse/SECURITY-746 and https://issues.jboss.org/browse/SECURITY-876
所以这是 JBoss 中的一个已知错误。如果你想解决它,我的建议是联系 JBoss。
我用于 AS 7 分支的替代解决方法是提供我自己修改的 org.jboss.as.web.security.jaspi.WebJASPIAuthenticator
实现,但随后您将 运行 立即进入 SECURITY-746,因此您需要自定义be.mycompany.api.authentication.jaspi.MycompanyAuthModule
你用过的模块。
(最新消息:请参阅我的其他答案以获得 "definitive" 解决方案)。
更新:
从 RedHat 获得了一个测试补丁并且它有效 :-)
我会在有更多信息可用时更新此答案。
更新 2:RedHat 说补丁应该在 7.3.3 中......但我认为它还不完整(发现另一个用例,它不起作用)。 (支持案例 01440434)
更新 3:除了工作补丁之外,我的身份验证模块中还有一个解决方法,使其适用于 JAX-RS 和 EJB:
// TODO: remove this when fixed in JBoss - WORKAROUND to get authentication to propagate to EJBs
SecurityContext oldContext = SecurityContextAssociation.getSecurityContext();
SubjectInfo subjectInfo = oldContext.getSubjectInfo();
subjectInfo.setAuthenticatedSubject(serviceSubject);
SecurityContextAssociation.setPrincipal(degroofPrincipal);
serviceSubject.getPrincipals().add(degroofPrincipal);
...但无论出于何种原因,它在 JSF 上下文中不起作用。
参见 Arjan Tijms 提供的 link、https://github.com/javaeekickoff/jboss-as-jaspic-patch/tree/master/src/main/java/patch/jboss。这确实适用于 7.4 的一些更改(删除日志记录,找到正确的 jar,一些自定义更改以使其编译)。
如有必要,我可以分享这个,但我刚刚在 RedHat 上打开了支持案例 01494061。希望他们最终会修补它...
Red Hat 目前的回答:
嗨 ---,
是的,我认为 Arjan Tijms 提供的信息是正确的。对于要传播到 ejb 层的主体,必须将其放入主题中。这适用于 [1] 中所示的方法。但是,它之所以有效,是因为 HTTPBasicServerAuthModule 遵从 JAAS/JBossWebRealm 来处理身份验证。这将设置主题,以便它将主体传播到 ejb 层。
我正在研究建议的增强功能并与我们的工程师讨论。
我会在下周初提供更新。
谢谢,
[1] https://developer.jboss.org/wiki/JBossAS7EnablingJASPIAuthenticationForWebApplications
看来Red Hat 终于修复了这个bug。我得到了一个在 JBoss EAP 6.4.3 中运行良好的官方补丁。
对于那些感兴趣的人,我的支持案例号是 01440434,补丁文件名是
通过谷歌搜索,我找到了 https://bugzilla.redhat.com/show_bug.cgi?id=1243553 and https://github.com/wildfly/wildfly/pull/7469/files
他们也谈论 https://github.com/jbossas/jboss-eap/pull/2480 但我得到了 404。
没有在 Wildfly 中尝试过,但我喜欢这个修复的简单性。
但仍有一些案例似乎已被打破(例如@RolesAllowed 似乎不起作用),但我会为此打开新的支持案例,因为我在第一次时并没有特别要求这个支持案例。
我有一个自定义的 JSR-196 模块,它基本上委托给一个服务,该服务将角色委托给 OAuth "grants" 调用。
它在 servlet 中工作(request.getUserPrincipal() 工作正常)。
它不会传播到 EJB 调用,其中 SessionContext.getCallerPrincipal() returns 具有 "anonymous" 而不是预期的用户名/角色的 SimplePrincipal。
MycompanyPrincipal 是一个简单的 class,具有简单的 getName() 和一些自定义属性。
好像SubjectInfo.getAuthenticatedSubject()没有本金
我设法为此做了一个丑陋的解决方法,请参阅下面的“// WORKAROUND”。
不过,我还是想以正确的方式去做(如果可能的话,甚至 standard/portable)。
这是我在 standalone.xml 中定义我的安全域的地方:
<security-domain name="mycompany" cache-type="default">
<authentication-jaspi>
<login-module-stack name="lm-stack">
<login-module code="UsersRoles" flag="required">
<module-option name="usersProperties" value="../standalone/configuration/jaspi-users.properties"/>
<module-option name="rolesProperties" value="../standalone/configuration/jaspi-roles.properties"/>
</login-module>
</login-module-stack>
<auth-module code="be.mycompany.api.authentication.jaspi.MycompanyAuthModule" flag="required" login-module-stack-ref="lm-stack"/>
</authentication-jaspi>
</security-domain>
这是我的 jboss-web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<jboss-web>
<context-root>api/rules/dev</context-root>
<security-domain>mycompany</security-domain>
<valve>
<class-name>org.jboss.as.web.security.jaspi.WebJASPIAuthenticator</class-name>
</valve>
</jboss-web>
模块本身是我的应用程序的一部分(我的 war 中的一个 jar)。 EJB 在其他 JAR 中定义,它们也以 WEB-INF/lib.
结尾serviceSubject.getPrincipals().add(degroofPrincipal)
这是我的模块(将 ejb 调用更改为静态方法调用):
package be.mycompany.api.authentication.jaspi;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.message.AuthException;
import javax.security.auth.message.AuthStatus;
import javax.security.auth.message.MessageInfo;
import javax.security.auth.message.MessagePolicy;
import javax.security.auth.message.callback.CallerPrincipalCallback;
import javax.security.auth.message.callback.GroupPrincipalCallback;
import javax.security.auth.message.config.ServerAuthContext;
import javax.security.auth.message.module.ServerAuthModule;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.HttpHeaders;
import org.jboss.security.SecurityContext;
import org.jboss.security.SecurityContextAssociation;
import org.jboss.security.SubjectInfo;
/**
*
* @author devyam
*/
public class MycompanyAuthModule implements ServerAuthModule, ServerAuthContext {
private static final String BEARER_PREFIX = "bearer ";
private CallbackHandler handler;
private final Class<?>[] supportedMessageTypes = new Class[]{HttpServletRequest.class, HttpServletResponse.class};
protected String delegatingLoginContextName = null;
// private MycompanyAuthenticationService mycompanyAuthenticationService;
/**
* <p>
* Creates an instance of {@code HTTPBasicServerAuthModule}.
* </p>
*/
public MycompanyAuthModule() {
// lookupMycompanyAuthenticationService();
}
/**
* <p>
* Creates an instance of {@code HTTPBasicServerAuthModule} with the
* specified delegating login context name.
* </p>
*
* @param delegatingLoginContextName the name of the login context
* configuration that contains the JAAS modules that are to be called by
* this module.
*/
public MycompanyAuthModule(String delegatingLoginContextName) {
this();
this.delegatingLoginContextName = delegatingLoginContextName;
}
@Override
public void initialize(MessagePolicy requestPolicy,
MessagePolicy responsePolicy, CallbackHandler handler,
@SuppressWarnings("rawtypes") Map options) throws AuthException {
this.handler = handler;
}
/**
* WebLogic 12c calls this before Servlet is called, Geronimo v3 after,
* JBoss EAP 6 and GlassFish 3.1.2.2 don't call this at all. WebLogic
* (seemingly) only continues if SEND_SUCCESS is returned, Geronimo
* completely ignores return value.
*/
@Override
public AuthStatus secureResponse(MessageInfo messageInfo, Subject serviceSubject) throws AuthException {
return AuthStatus.SEND_SUCCESS;
}
@Override
public AuthStatus validateRequest(MessageInfo messageInfo, Subject clientSubject, Subject serviceSubject) throws AuthException {
HttpServletRequest request = (HttpServletRequest) messageInfo.getRequestMessage();
HttpServletResponse response = (HttpServletResponse) messageInfo.getResponseMessage();
String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
if (authHeader != null && authHeader.startsWith(BEARER_PREFIX)) {
String token = authHeader.substring(BEARER_PREFIX.length());
MycompanyPrincipal mycompanyPrincipal = MycompanyAuthenticationService.createPrincipal(token);
List<String> groups = MycompanyAuthenticationService.getGroups(mycompanyPrincipal);
String[] groupArray = groups.toArray(new String[0]);
CallerPrincipalCallback callerPrincipalCallback = new CallerPrincipalCallback(clientSubject, mycompanyPrincipal);
GroupPrincipalCallback groupPrincipalCallback = new GroupPrincipalCallback(clientSubject, groupArray);
try {
handler.handle(new Callback[]{callerPrincipalCallback, groupPrincipalCallback});
} catch (IOException | UnsupportedCallbackException exception) {
throw new RuntimeException(exception);
}
//////// WORKAROUND: doesn't work without this in EJBs!
SecurityContext oldContext = SecurityContextAssociation.getSecurityContext();
SubjectInfo subjectInfo = oldContext.getSubjectInfo();
subjectInfo.setAuthenticatedSubject(serviceSubject);
SecurityContextAssociation.setPrincipal(mycompanyPrincipal);
serviceSubject.getPrincipals().add(mycompanyPrincipal);
////////////// end of workaround
return AuthStatus.SUCCESS;
}
response.setStatus(401);
return AuthStatus.FAILURE;
}
/**
* A compliant implementation should return HttpServletRequest and
* HttpServletResponse, so the delegation class {@link ServerAuthContext}
* can choose the right SAM to delegate to. In this example there is only
* one SAM and thus the return value actually doesn't matter here.
*/
@Override
public Class<?>[] getSupportedMessageTypes() {
return supportedMessageTypes;
}
@Override
public void cleanSubject(MessageInfo messageInfo, Subject subject)
throws AuthException {
}
// private void lookupMycompanyAuthenticationService() throws RuntimeException {
// try {
// BeanManager beanManager = InitialContext.doLookup("java:comp/BeanManager");
// Bean<?> mycompanyAuthenticationServiceBean = beanManager.getBeans(MycompanyAuthenticationService.class).iterator().next();
// CreationalContext creationalContext = beanManager.createCreationalContext(mycompanyAuthenticationServiceBean);
// mycompanyAuthenticationService = (MycompanyAuthenticationService) beanManager.getReference(mycompanyAuthenticationServiceBean, MycompanyAuthenticationService.class, creationalContext);
// } catch (NamingException exception) {
// throw new RuntimeException(exception);
// }
// }
}
不幸的是,尽管 JBoss 工程师尽了最大努力,但将经过身份验证的身份从 Servlet 传播到 EJB 对 JBoss 来说是一个永无止境的故事。
有大约 6 个单独的错误需要修复,因此您甚至可以到达现在的 JBoss AS 7.4(JBoss EAP 6.3 我假设),并且在此之后有几个错误。
这个特定的错误是 https://issues.jboss.org/browse/SECURITY-745 and was filed almost 2 years ago, but still open for the AS 7/EAP 6 branch. This one came right after https://issues.jboss.org/browse/SECURITY-744,它被列为开放,但我认为它实际上已修复。
WF 8/EAP 7 分支没有这个错误,但是两个分支都有 https://issues.jboss.org/browse/SECURITY-746 and https://issues.jboss.org/browse/SECURITY-876
所以这是 JBoss 中的一个已知错误。如果你想解决它,我的建议是联系 JBoss。
我用于 AS 7 分支的替代解决方法是提供我自己修改的 org.jboss.as.web.security.jaspi.WebJASPIAuthenticator
实现,但随后您将 运行 立即进入 SECURITY-746,因此您需要自定义be.mycompany.api.authentication.jaspi.MycompanyAuthModule
你用过的模块。
(最新消息:请参阅我的其他答案以获得 "definitive" 解决方案)。
更新:
从 RedHat 获得了一个测试补丁并且它有效 :-)
我会在有更多信息可用时更新此答案。
更新 2:RedHat 说补丁应该在 7.3.3 中......但我认为它还不完整(发现另一个用例,它不起作用)。 (支持案例 01440434)
更新 3:除了工作补丁之外,我的身份验证模块中还有一个解决方法,使其适用于 JAX-RS 和 EJB:
// TODO: remove this when fixed in JBoss - WORKAROUND to get authentication to propagate to EJBs
SecurityContext oldContext = SecurityContextAssociation.getSecurityContext();
SubjectInfo subjectInfo = oldContext.getSubjectInfo();
subjectInfo.setAuthenticatedSubject(serviceSubject);
SecurityContextAssociation.setPrincipal(degroofPrincipal);
serviceSubject.getPrincipals().add(degroofPrincipal);
...但无论出于何种原因,它在 JSF 上下文中不起作用。
参见 Arjan Tijms 提供的 link、https://github.com/javaeekickoff/jboss-as-jaspic-patch/tree/master/src/main/java/patch/jboss。这确实适用于 7.4 的一些更改(删除日志记录,找到正确的 jar,一些自定义更改以使其编译)。
如有必要,我可以分享这个,但我刚刚在 RedHat 上打开了支持案例 01494061。希望他们最终会修补它...
Red Hat 目前的回答:
嗨 ---,
是的,我认为 Arjan Tijms 提供的信息是正确的。对于要传播到 ejb 层的主体,必须将其放入主题中。这适用于 [1] 中所示的方法。但是,它之所以有效,是因为 HTTPBasicServerAuthModule 遵从 JAAS/JBossWebRealm 来处理身份验证。这将设置主题,以便它将主体传播到 ejb 层。
我正在研究建议的增强功能并与我们的工程师讨论。
我会在下周初提供更新。
谢谢,
[1] https://developer.jboss.org/wiki/JBossAS7EnablingJASPIAuthenticationForWebApplications
看来Red Hat 终于修复了这个bug。我得到了一个在 JBoss EAP 6.4.3 中运行良好的官方补丁。
对于那些感兴趣的人,我的支持案例号是 01440434,补丁文件名是
通过谷歌搜索,我找到了 https://bugzilla.redhat.com/show_bug.cgi?id=1243553 and https://github.com/wildfly/wildfly/pull/7469/files
他们也谈论 https://github.com/jbossas/jboss-eap/pull/2480 但我得到了 404。
没有在 Wildfly 中尝试过,但我喜欢这个修复的简单性。
但仍有一些案例似乎已被打破(例如@RolesAllowed 似乎不起作用),但我会为此打开新的支持案例,因为我在第一次时并没有特别要求这个支持案例。