46

我发现这篇文章对非 ajax 请求很有用How to handle session expire and ViewExpiredException in JSF 2? 但是当我使用 AJAX 调用提交时,我无法使用它。

假设在 primefaces 对话框中,我正在使用 AJAX 进行发布请求,并且会话已经超时。我看到我的页面卡住了。

如何解决这种情况,以便当我使用 AJAX 发布时,我可以将他重定向到我的视图过期页面,然后将他转发到类似于上面链接中的解决方案的登录页面?

JSF2/Primefaces/Glassfish

4

6 回答 6

52

默认情况下,在 ajax 请求期间抛出的异常在客户端完全没有反馈。只有当您在项目阶段设置为Development和使用的情况下运行 Mojarra 时<f:ajax>,您才会收到带有异常类型和消息的纯 JavaScript 警报。但除此之外,在 PrimeFaces 中,默认情况下根本没有反馈。但是,您可以在服务器日志和 ajax 响应中看到异常(在 webbrowser 的开发人员工具集的“网络”部分)。

您需要实现一个自定义,当队列中ExceptionHandler有 a 时,它基本上完成以下工作:ViewExpiredException

String errorPageLocation = "/WEB-INF/errorpages/expired.xhtml";
context.setViewRoot(context.getApplication().getViewHandler().createView(context, errorPageLocation));
context.getPartialViewContext().setRenderAll(true);
context.renderResponse();

或者,您可以使用 JSF 实用程序库OmniFaces。它有一个FullAjaxExceptionHandler正是为了这个目的(源代码在这里,展示演示在这里)。

也可以看看:

于 2012-06-26T11:14:11.623 回答
18

@BalusC 的答案和这篇文章的合并,我解决了我的问题!

我的 ExceptionHandlerWrapper:

public class CustomExceptionHandler extends ExceptionHandlerWrapper {

    private ExceptionHandler wrapped;

    CustomExceptionHandler(ExceptionHandler exception) {
        this.wrapped = exception;
    }

    @Override
    public ExceptionHandler getWrapped() {
        return wrapped;
    }

    @Override
    public void handle() throws FacesException {
        final Iterator<ExceptionQueuedEvent> i = getUnhandledExceptionQueuedEvents().iterator();
        while (i.hasNext()) {
            ExceptionQueuedEvent event = i.next();
            ExceptionQueuedEventContext context
                    = (ExceptionQueuedEventContext) event.getSource();

            // get the exception from context
            Throwable t = context.getException();

            final FacesContext fc = FacesContext.getCurrentInstance();
            final Map<String, Object> requestMap = fc.getExternalContext().getRequestMap();
            final NavigationHandler nav = fc.getApplication().getNavigationHandler();

            //here you do what ever you want with exception
            try {

                //log error ?
                //log.log(Level.SEVERE, "Critical Exception!", t);
                if (t instanceof ViewExpiredException) {
                    requestMap.put("javax.servlet.error.message", "Session expired, try again!");
                    String errorPageLocation = "/erro.xhtml";
                    fc.setViewRoot(fc.getApplication().getViewHandler().createView(fc, errorPageLocation));
                    fc.getPartialViewContext().setRenderAll(true);
                    fc.renderResponse();
                } else {
                    //redirect error page
                    requestMap.put("javax.servlet.error.message", t.getMessage());
                    nav.handleNavigation(fc, null, "/erro.xhtml");
                }

                fc.renderResponse();
                // remove the comment below if you want to report the error in a jsf error message
                //JsfUtil.addErrorMessage(t.getMessage());
            } finally {
                //remove it from queue
                i.remove();
            }
        }
        //parent hanle
        getWrapped().handle();
    }
}

我的异常处理器工厂:

public class CustomExceptionHandlerFactory extends ExceptionHandlerFactory {

    private ExceptionHandlerFactory parent;

    // this injection handles jsf
    public CustomExceptionHandlerFactory(ExceptionHandlerFactory parent) {
        this.parent = parent;
    }

    @Override
    public ExceptionHandler getExceptionHandler() {
        ExceptionHandler handler = new CustomExceptionHandler(parent.getExceptionHandler());
        return handler;
    }

}

我的面孔-config.xml

<?xml version='1.0' encoding='UTF-8'?>
<faces-config version="2.2"
              xmlns="http://xmlns.jcp.org/xml/ns/javaee"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd">

    <factory>
        <exception-handler-factory>
            your.package.here.CustomExceptionHandlerFactory
        </exception-handler-factory>
    </factory>
</faces-config>
于 2015-02-19T18:23:52.047 回答
3

我在生产模式下使用 Mojarra 2.1.7 和 JBoss 7。会话到期后,AJAX 调用返回错误 XML 文档。您可以使用 f:ajax 的常用 onerror 处理程序轻松捕获此错误。

<script type="text/javascript">
    function showError(data) {
        alert("An error happened");
        console.log(data);
    }
</script>

<h:commandLink action="...">
    <f:ajax execute="..." render="..." onerror="showError"/>
</h:commandLink>
于 2012-10-06T00:55:46.853 回答
1

我已将它包含在我的 ViewExpiredExceptionHandler 类中,它在 WAS 中对我来说效果很好

    public void handle() throws FacesException {
    FacesContext facesContext = FacesContext.getCurrentInstance();
                 for (Iterator<ExceptionQueuedEvent> iter = getUnhandledExceptionQueuedEvents()
            .iterator(); iter.hasNext();) {
        Throwable exception = iter.next().getContext().getException();

        if (exception instanceof ViewExpiredException) {


            final ExternalContext externalContext = facesContext
                    .getExternalContext();

            try {


                facesContext.setViewRoot(facesContext.getApplication()
                        .getViewHandler()
                        .createView(facesContext, "/Login.xhtml"));     //Login.xhtml is the page to to be viewed. Better not to give /WEB-INF/Login.xhtml
                externalContext.redirect("ibm_security_logout?logoutExitPage=/Login.xhtml");    //  when browser back button is pressed after session timeout, I used this.         
                facesContext.getPartialViewContext().setRenderAll(true);
                facesContext.renderResponse();

            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } finally {
                iter.remove();
            }
        }

    }

    getWrapped().handle();
}

希望这可以帮助

于 2013-03-12T09:30:11.437 回答
1

我遇到了这个问题,当用户在会话超时后执行任何操作时,需要显示一个确认弹出窗口,我提出的解决方案是:

<security:http use-expressions="true" auto-config="true" entry-point-ref="authenticationEntryPoint">
            <security:intercept-url pattern="/common/auth/**" access="permitAll" />
            <security:intercept-url pattern="/javax.faces.resource/**" access="permitAll" />
            <security:intercept-url pattern="/**/   *.*" access="hasRole('ROLE_ADMIN')" />
            <security:form-login login-page="/common/auth/login.jsf" />
            <!-- <security:remember-me key="secret" services-ref="rememberMeServices" /> -->
            <security:logout invalidate-session="true" logout-success-url="/common/auth/login.jsf" />
        </security:http>
        <bean id="authenticationEntryPoint" class="com.x.y.MyRedirectEntryPoint" >
           <property name="loginFormUrl" value="/common/auth/login.jsf"/>
        </bean>

MyRedirectEntryPoint 应该扩展 AuthenticationProcessingFilterEntryPoint 并覆盖开始方法

public void commence(HttpServletRequest request, HttpServletResponse response,   AuthenticationException authException)
        throws IOException, ServletException {
    boolean ajaxRedirect = request.getHeader("faces-request") != null
            && request.getHeader("faces-request").toLowerCase().indexOf("ajax") > -1;
    if (ajaxRedirect) {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null) {
            response.sendError(403);

        }
    } else {

        super.commence(request, response, authException);
    }
}

现在你可以简单地绑定一个回调 javascript 函数来捕获抛出的 403 错误并做任何你想做的事情:

$(document).bind('ajaxError',
                    function(event, request, settings, exception){
                          if (request.status==403){
                             //do whatever you wanted may be show a popup or just redirect
                             window.location = '#{request.contextPath}/';
                             }
                             });
于 2013-09-10T09:28:41.010 回答
1

对我来说,一个简单的客户端 javascript 处理程序有效:

function handleAjaxExpired(xhr,status,args) {
    // handler for "oncomplete" ajax callback
    if ( xhr.responseXML.getElementsByTagName('error-name').length ) {
        // "<error-name>" tag is present -> check for "view expired" exception
        html = xhr.responseXML.getElementsByTagName('error-name')[0].innerHTML;

        if ( html.indexOf('ViewExpiredException') > -1 ) {
            // view expired exception thrown
            // do something / reload page
            if ( confirm('session expired -> reload page?') ) {
                document.location.href=document.location.href;
            }
        }
    }
}

此处理程序在触发 UI 元素时从“oncomplete”属性调用,例如这里从 Primefaces 数据表中的 rowSelect 事件调用:

<p:ajax event="rowSelect" oncomplete="handleAjaxExpired(xhr,status,args)" />

更新:为避免向每个启用 ajax 的元素添加“oncomplete”属性,此 javascript 代码在所有 ajax 响应中全局搜索错误:

(function() {
    // intercept all ajax requests
    var origXHROpen = XMLHttpRequest.prototype.open;

    XMLHttpRequest.prototype.open = function() {

        this.addEventListener('load', function() {
            handleAjaxExpired(this);
        });
        origXHROpen.apply(this, arguments);
    };
})();

此代码使 PrimeFaces UI 元素中的“oncomplete”属性过时。

于 2020-02-28T10:22:01.380 回答