Модуль Jaspic не распространяет принципал на локальный EJB в JBoss 7.4

У меня есть пользовательский модуль JSR-196, который в основном делегирует службе, которая делегирует роли вызову OAuth "grants".

Он работает от сервлета (request.getUserPrincipal() работает нормально).

Он не распространяется на вызовы EJB, где SessionContext.getCallerPrincipal() возвращает SimplePrincipal с "анонимным" вместо ожидаемого имени пользователя / ролей.

MycompanyPrincipal - это простой класс с простым getName() и некоторыми пользовательскими свойствами.

Кажется, что SubjectInfo.getAuthenticatedSubject() не имеет принципала.

Мне удалось сделать уродливый обходной путь для этого, см. "// ВРЕМЯ РАБОТЫ" ниже.

Тем не менее, я бы хотел сделать это правильно (даже стандартным / портативным, если это возможно).

Здесь я определяю свой домен безопасности в 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);
//        }
//    }
}

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-746 и 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 модуль, который вы использовали в любом случае.

(ПОСЛЕДНИЕ НОВОСТИ: см. Мой другой ответ для "окончательного" решения).

ОБНОВИТЬ:

Получил тестовый патч от 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.

См. Ссылку Арджана Тиймса, https://github.com/javaeekickoff/jboss-as-jaspic-patch/tree/master/src/main/java/patch/jboss. Это работает с некоторыми изменениями для 7.4 (удаление журнала, поиск правильных jar-файлов, некоторые пользовательские изменения для его компиляции).

Я могу поделиться этим в случае необходимости, но я только что открыл для этого случай поддержки 01494061 в RedHat. Надеюсь, они в конце концов исправят это...


Ответ Red Hat на данный момент:

Привет ---,

Да, я думаю, что предоставленная Арджаном Тиймс информация верна. Чтобы принципал распространялся на слой ejb, его нужно поместить в тему. Это работает с подходом, показанным в [1]. Однако это работает, потому что HTTPBasicServerAuthModule обращается к JAAS/JBossWebRealm для обработки аутентификации. Это настраивает субъект так, чтобы он распространял принципал на уровень ejb.

Я изучаю предложенные улучшения и обсуждаю это с нашими инженерами.

Я предоставлю обновление в начале следующей недели.

Спасибо,

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

Наконец, похоже, Red Hat исправила ошибку. Я получил официальный патч, который хорошо работает в JBoss EAP 6.4.3.

Для тех, кто заинтересовался, мой номер поддержки был 01440434, а имя файла патча было

Поиск в Google приводит меня к https://bugzilla.redhat.com/show_bug.cgi?id=1243553 и https://github.com/wildfly/wildfly/pull/7469/files

Они также говорят о https://github.com/jbossas/jboss-eap/pull/2480 но я получаю 404 на этом.

Не пробовал в Wildfly, но мне нравится простота этого исправления.

Хотя все еще есть случаи, которые кажутся неработающими (например, @RolesAllowed, похоже, не работает), но я открою новые случаи поддержки для этого, так как я специально не просил об этом в моем первом случае поддержки.

Другие вопросы по тегам