ViewExpiredException не генерируется при запросе ajax, если страница JSF защищена j_security_check

У меня есть страница JSF, которая не защищена j_security_check, Я выполняю следующие шаги:

  1. Откройте страницу JSF в браузере.
  2. Перезагрузите сервер.
  3. Нажмите кнопку команды на странице JSF, чтобы инициировать вызов ajax.

Firebug показывает, что ViewExpiredException поднят, как и ожидалось.

Сообщение:

javax.faces.ViewState=8887124636062606698:-1513851009188353364

Отклик:

<partial-response>
<error>
<error-name>class javax.faces.application.ViewExpiredException</error-name>
<error-message>viewId:/viewer.xhtml - View /viewer.xhtml could not be restored.</error-message>
</error>
</partial-response>

Тем не менее, как только я настрою страницу для защиты j_security_check и выполните те же самые шаги, перечисленные выше, как ни странно (для меня) ViewExpiredException больше не поднимается. Вместо этого ответ - это просто новое состояние просмотра.

Сообщение:

javax.faces.ViewState=-4873187770744721574:8069938124611303615

Отклик:

<partial-response>
<changes>
<update id="javax.faces.ViewState">234065619769382809:-4498953143834600826</update>
</changes>
</partial-response>

Может кто-нибудь помочь мне понять это? Я ожидаю, что оно вызовет исключение, чтобы я мог обработать это исключение и показать страницу с ошибкой. Теперь он просто отвечает новым ViewState, моя страница застряла без визуальной обратной связи.

2 ответа

Решение

Я смог воспроизвести вашу проблему. Здесь происходит то, что контейнер вызывает RequestDispatcher#forward() на страницу входа, как указано в ограничении безопасности. Однако, если страница входа сама по себе является страницей JSF, то FacesServlet будет также вызываться по перенаправленному запросу. Поскольку запрос является форвардом, это просто создаст новое представление для перенаправленного ресурса (страницы входа в систему). Тем не менее, так как это запрос AJAX и нет render информация (весь запрос POST в основном отбрасывается во время проверки безопасности), будет возвращено только состояние просмотра.

Обратите внимание, что если бы страница входа не была страницей JSF (например, JSP или обычный HTML), то запрос ajax вернул бы весь HTML-вывод страницы как ответ ajax, который не разбирается JJF ajax и интерпретируется как "пустой" ответ.

К сожалению, он работает "как задумано". Я подозреваю, что в спецификации JSF есть некоторые упущения в отношении проверок ограничений безопасности для запросов AJAX. Причина в конце концов понятна и, к счастью, легко решаема. Только вы на самом деле не хотите показывать страницу с ошибкой здесь, но вместо этого просто страницу входа в систему в полном объеме, точно так же, как это происходит во время не-AJAX-запроса. Вам просто нужно проверить, является ли текущий запрос ajax-запросом и был ли он перенаправлен на страницу входа в систему, затем вам нужно отправить специальный ответ ajax "перенаправить", чтобы весь вид был изменен.

Вы можете достичь этого с PhaseListener следующее:

public class AjaxLoginListener implements PhaseListener {

    @Override
    public PhaseId getPhaseId() {
        return PhaseId.RESTORE_VIEW;
    }

    @Override
    public void beforePhase(PhaseEvent event) {
        // NOOP.
    }

    @Override
    public void afterPhase(PhaseEvent event) {
        FacesContext context = event.getFacesContext();
        HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest();
        String originalURL = (String) request.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI);
        String loginURL = request.getContextPath() + "/login.xhtml";

        if (context.getPartialViewContext().isAjaxRequest()
            && originalURL != null
            && loginURL.equals(request.getRequestURI()))
        {
            try {
                context.getExternalContext().invalidateSession();
                context.getExternalContext().redirect(originalURL);
            } catch (IOException e) {
                throw new FacesException(e);
            }
        }
    }
}

Обновите это решение, поскольку OmniFaces 1.2 была встроена в OmniPartialViewContext, Так что если вы уже используете OmniFaces, то эта проблема полностью прозрачно решена, и вам не нужно настраивать PhaseListener за это.

Вышеупомянутое решение AjaxLoginListener работает для меня. Интересно, что мы используем omnifaces 3.11.1, но OmniPartialViewContext не работает в моем сценарии. Это связано с тем, что проверка для loginViewId не соответствует текущему viewId, поскольку в моем web.xml есть страница с ошибкой для org.jboss.weld.contexts.NonexistentConversationException. Обратите внимание, что когда для меня запускается AjaxLoginListener, он генерирует исключение при вызове context.getExternalContext(). InvalidateSession(); поэтому он никогда не вызывает redirect (). Поэтому я не уверен, что мой сценарий в точности совпадает с исходным в этой ветке. Вот шаги, которые я использую для воссоздания своего сценария:

  1. Посетите xhtml-страницу с помощью командной кнопки ajax.
  2. Подождите, пока истечет время ожидания сеанса.
  3. Нажмите кнопку команды ajax.
  4. Пользователь перенаправляется на страницу с ошибкой, сопоставленную с исключением NonexistentConversationException в web.xml.
  5. Щелкните ссылку на этой странице, которая запрашивает защищенный URL-адрес.
  6. Система показывает страницу входа - логин.
  7. Щелкните ссылку, которая приведет вас на страницу xhtml, содержащую кнопку команды ajax на шаге 1.
  8. Система показывает частичный ответ, содержащий содержимое страницы ошибки NonexistentConversationException.

Возможно ли, что AjaxLoginListener работает, потому что он сопоставлен с PhaseId.RESTORE_VIEW, тогда как OmniPartialViewContext сопоставлен с PhaseId.RENDER_RESPONSE?

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