【问题标题】:Session timeout and ViewExpiredException handling on JSF/PrimeFaces ajax requestJSF/PrimeFaces ajax 请求上的会话超时和 ViewExpiredException 处理
【发布时间】:2012-06-27 12:38:22
【问题描述】:

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

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

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

JSF2/Primefaces/Glassfish

【问题讨论】:

    标签: ajax jsf-2 primefaces session-timeout viewexpiredexception


    【解决方案1】:

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

    您需要实现一个自定义 ExceptionHandler,当队列中有 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 正是为了这个目的(源代码here,展示演示here)。

    另见:

    【讨论】:

    • 嗨,BalusC.. 永远感谢。我浏览了 Omnifaces 链接,我只想问如果我已经在使用 Primefaces 并将 Omnifaces 添加到组合中是否不会发生冲突?另外关于我的问题,我仍然需要阅读有关 FullAjaxExceptionHandler..
    • 绝对不是。更重要的是,Showcase 应用程序使用 PrimeFaces。 OmniFaces 只是一个实用程序库,其实用程序只能由您自己的命令/配置使用。
    • 感谢 BalusC.. 你非常乐于助人!
    • @BalusC 如图所示,我添加了omnifaces FullAjaxExceptionHandlerFactory,但页面仍然卡住。如何查看是否抛出了会话超时异常?
    • 按钮并发布SSCCE。
    【解决方案2】:

    @BalusC 和this post 的答案合并,我解决了我的问题!

    我的 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();
        }
    }
    

    我的 ExceptionHandlerFactory:

    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>
    

    【讨论】:

      【解决方案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>
      

      【讨论】:

      • 您必须将该处理程序添加到页面上的每个 f/p:ajax 中。不是一个可行的解决方案。
      • @Łukasz웃Lツ 您也可以使用jsf.ajax.addOnError(在 javascript 中)在全局范围内添加它。
      • 如果您的控制台未在 IE 中打开,也容易出错。
      【解决方案4】:

      我已将它包含在我的 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();
      }
      

      希望对你有帮助

      【讨论】:

        【解决方案5】:

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

        <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}/';
                                     }
                                     });
        

        【讨论】:

          【解决方案6】:

          对我来说,一个简单的客户端 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 元素中的“不完整”属性过时。

          【讨论】:

          • 那么,你需要把它放在每个 Ajax 调用的每个 oncomplete 中吗?
          • 是的,我没有发现 Ajax 响应的通用处理程序(“oncomplete”)。 stackoverflow.com/questions/14764619/… 中的建议对我不起作用。
          • 我以前尝试过这个 - 运气不好,可能在我的环境中太复杂了 ;-) 但与此同时,我找到了一个解决方案来拦截所有 ajax 响应并更新了原始帖子。非常感谢您的启发!
          猜你喜欢
          • 2012-11-24
          • 2013-05-27
          • 1970-01-01
          • 2016-02-03
          • 2013-06-10
          • 2010-11-03
          • 2014-11-24
          • 2013-12-15
          相关资源
          最近更新 更多