Исключение ViewExpiredException не создается по запросу ajax, если страница JSF защищена проверкой безопасности j

у меня есть страница 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 больше не поднимали. Вместо этого reponse-это просто новое состояние представления.

сообщение:

javax.faces.ViewState=-4873187770744721574:8069938124611303615

ответ:

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

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

1 ответов


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

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

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

вы можете достичь этого с помощью 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 для этого.