4

我有一个自定义的 JSR-196 模块,它基本上委托给一个服务,该服务将角色委托给 OAuth“授权”调用。

它确实可以从 servlet 工作(request.getUserPrincipal() 工作正常)。

它不会传播到 EJB 调用,其中 SessionContext.getCallerPrincipal() 返回具有“匿名”而不是预期用户名/角色的 SimplePrincipal。

MycompanyPrincipal 是一个简单的类,具有简单的 getName() 和一些自定义属性。

似乎 SubjectInfo.getAuthenticatedSubject() 没有主体。

我设法为此做出了一个丑陋的解决方法,请参阅下面的“// WORKAROUND”。

尽管如此,我还是想以正确的方式来做(如果可能的话,甚至是标准/便携式)。

这是我在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>

该模块本身是我的应用程序的一部分(我的战争中的一个罐子)。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);
//        }
//    }
}
4

3 回答 3

3

不幸的是,将经过身份验证的身份从 Servlet 传播到 EJB 对 JBoss 来说是一个永无止境的故事,尽管 JBoss 工程师尽了最大的努力。

有一些 6 个单独的错误需要修复,因此您甚至可以达到您现在在 JBoss AS 7.4 中的程度(我假设是 JBoss EAP 6.3),在此之后还有一些错误。

这个特殊的错误是https://issues.jboss.org/browse/SECURITY-745并在大约 2 年前提交,但仍然对 AS 7/EAP 6 分支开放。这个是在https://issues.jboss.org/browse/SECURITY-744之后出现的,它被列为开放,但我认为它实际上是固定的。

WF 8/EAP 7 分支没有这个错误,但两个分支都受到https://issues.jboss.org/browse/SECURITY-746https://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您使用的自定义模块。

于 2015-05-05T22:25:56.633 回答
3

(最新消息:有关“确定性”解决方案,请参阅我的其他答案)。

更新:

从 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 提供的链接,https://github.com/javaeekickoff/jboss-as-jaspic-patch/tree/master/src/main/java/patch/jboss。这确实适用于 7.4 的一些更改(删除日志记录、查找正确的 jar、一些自定义更改以使其编译)。

如有必要,我可以分享这个,但我刚刚在 RedHat 上为此打开了支持案例 01494061。希望他们最终会修补它...


红帽现在的回答是:

你好 - -,

是的,我认为 Arjan Tijms 提供的信息是正确的。要将主体传播到 ejb 层,必须将其放入主题中。这适用于 [1] 中所示的方法。但是,它之所以有效,是因为 HTTPBasicServerAuthModule 遵循 JAAS/JBossWebRealm 来处理身份验证。这设置了主题,以便它将主体传播到 ejb 层。

我正在研究建议的增强功能并与我们的工程师讨论。

我将在下周初提供更新。

谢谢,

[1] https://developer.jboss.org/wiki/JBossAS7EnablingJASPIAuthenticationForWebApplications

于 2015-05-08T11:38:10.857 回答
3

最后,Red Hat 似乎修复了这个错误。我得到了一个在 JBoss EAP 6.4.3 中运行良好的官方补丁。

对于那些感兴趣的人,我的支持案例编号是 01440434,补丁文件名是

谷歌搜索它导致我到https://bugzilla.redhat.com/show_bug.cgi?id=1243553https://github.com/wildfly/wildfly/pull/7469/files

他们还谈论https://github.com/jbossas/jboss-eap/pull/2480但我得到了 404。

没有在 Wildfly 中尝试过,但我喜欢这个修复的简单性。

虽然仍有一些案例似乎被打破(例如@RolesAllowed 似乎不起作用),但我会为此打开新的支持案例,因为我在第一个支持案例中没有专门要求这个。

于 2015-09-25T07:07:52.253 回答