(为什么我得到)在使用AJAX(使用OmniFaces)配置处理异常后,在不使用AJAX时转发/重定向到登录页面时出错

时间:2013-11-06 16:41:30

标签: jsf primefaces omnifaces

我有一个在Glassfish上运行的JSF应用程序[GlassFish Server开源版3.1.2.2(版本5); Mojarra 2.1.6(SNAPSHOT 20111206)]。我正在使用PrimeFaces 3.5和OmniFaces 1.5版。我正在使用基于表单的登录身份验证

<login-config>
    <auth-method>FORM</auth-method>
    <realm-name>reportingRealm</realm-name>
    <form-login-config>
        <form-login-page>/faces/login.xhtml</form-login-page>
        <form-error-page>/loginError.xhtml</form-error-page>
    </form-login-config>
</login-config>

在web.xml中定义了一个错误页面

<error-page>
  <exception-type>java.lang.Throwable</exception-type>
  <location>/faces/viewExpired.xhtml</location>
</error-page>

并具有以下用于servlet映射的URL模式(如果相关)

<servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>/faces/*</url-pattern>
</servlet-mapping>

应用程序最初没有使用AJAX。具体来说,我有一个不使用AJAX的p:commandButton。如果会话过期并单击此按钮,则异常处理按如下方式进行:显示登录页面;在(重新)输入登录凭据并登录时,会显示会话过期页面。选择再次登录会让您重新开始工作。我对此没有任何问题。 (请注意,原来我相信,我专门在错误页面引用ViewExpiredException,而不是抛出,因为它是现在配置,我描述既出现在使用的Throwable当使用ViewExpiredException,需要提醒的行为(S),在ViewExpiredException的情况我添加了一个额外的异常类型来满足OmniFaces的要求。)

在单击p:commandButton时,我将应用程序更改为使用AJAX。为了支持这一点,我将OmniFaces(我强烈推荐的解决方案)添加到我的项目中,并将form-login-page从'/login.xhtml'更改为'/faces/login.xhtml'(据我了解OmniFaces)文档,并给出我的配置,这是必要的;实际上,当form-login-page等于'/login.xhmtl'时,异常处理继续像以前一样使用非AJAX启用的控件,但什么都不会发生单击启用AJAX的控件时(没有更改显示的页面,也没有记录任何内容)。)

使用AJAX的p:commandButton,正在使用的OmniFaces和form-login-page更改为'/faces/login.xhtml',当会话过期并且我点击启用了AJAX的控件时,ViewExpiredException处理工作正常完美(实际上,我更喜欢在为非AJAX启用的控件处理ViewExpiredException时发生的序列上发生的序列:使用OmniFaces的序列是直接进入会话过期页面。选择再次登录然后你重新开始营业。)

但是,现在 - 如果会话已过期并且我点击了非AJAX启用的控件 - 我的浏览器窗口中会显示以下内容(现在显示在页面上的唯一内容)

XML Parsing Error: no element found
Location: http://localhost:8080/Reporting-war/faces/protected/multiUser.xhtml
Line Number 1, Column 1;

记录以下内容:

WARNING: ApplicationDispatcher[/Reporting-war] PWC1231: Servlet.service() for servlet Faces Servlet threw exception
javax.faces.application.ViewExpiredException: viewId:/login.xhtml - View /login.xhtml could not be restored.
    at com.sun.faces.lifecycle.RestoreViewPhase.execute(RestoreViewPhase.java:205)
    at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101)
...
    at java.lang.Thread.run(Thread.java:722)

WARNING: Unexpected error forwarding or redirecting to login page
javax.servlet.ServletException: viewId:/login.xhtml - View /login.xhtml could not be restored.
    at javax.faces.webapp.FacesServlet.service(FacesServlet.java:606)
    at org.apache.catalina.core.StandardWrapper.service(StandardWrapper.java:1550)
...
    at java.lang.Thread.run(Thread.java:722)
Caused by: javax.faces.application.ViewExpiredException: viewId:/login.xhtml - View /login.xhtml could not be restored.
    at com.sun.faces.lifecycle.RestoreViewPhase.execute(RestoreViewPhase.java:205)
    at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101)
...
    at javax.faces.webapp.FacesServlet.service(FacesServlet.java:593)
    ... 32 more

请注意,如果form-login-page的值为'/faces/login.xhtml',则无论OmniFaces是否正在使用,都会发生错误;我不相信这是OmniFaces的问题。但是,我不明白(a)它为什么会发生,或者(b)是否有一种方法可以维护我现有的配置(例如,保持我的servlet映射等)并使会话过期处理工作用于启用AJAX和非AJAX的控件。

multiUser.xhtml开头如下:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns:ui="http://java.sun.com/jsf/facelets"
                template="./mainTemplate.xhtml"
                xmlns:p="http://primefaces.org/ui"
                xmlns:h="http://java.sun.com/jsf/html"
                xmlns:c="http://java.sun.com/jsp/jstl/core"
                xmlns:f="http://java.sun.com/jsf/core"
                xmlns="http://www.w3.org/1999/xhtml">

    <ui:define name="top">
    </ui:define>

<ui:define name="content">
...

login.xhtm,完整,是:

<?xml version="1.0" encoding="UTF-8"?>
<!--
To change this template, choose Tools | Templates
and open the template in the editor.
-->
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>Login</title>
    </head>
    <body>
        <form method="POST" action="j_security_check">
            Username
            <input type="text" name="j_username"/>
            Password
            <input type="password" name="j_password"/>
            <input type="submit" value="Login"/>
        </form>
    </body>
</html>

viewExpired.xhtml,完整的是:

<?xml version="1.0" encoding="UTF-8"?>
<!--
To change this template, choose Tools | Templates
and open the template in the editor.
-->
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html">
    <head>
        <title>Expired Session</title>
    </head>
    <body>
        <div>
            <p>
                Your login session has expired.
            </p>
            <p>
                <h:form>
                    <h:link value="Login Again" outcome="home"/>
                </h:form>
            </p>
        </div>
    </body>
</html>

任何人都可以提供帮助,理解为什么会发生这种情况,并了解解决问题的方法,我们将不胜感激。感谢。

1 个答案:

答案 0 :(得分:1)

导致具体问题是因为默认情况下普通容器会保存POST请求的POST数据,这些POST请求会打到基于FORM的身份验证。当基于FORM的身份验证成功时,将恢复此POST数据(所有POST请求参数)。 JSF javax.faces.ViewState隐藏字段值也存在于POST数据中。

由于基于FORM的身份验证是通过POST执行的,javax.faces.ViewState隐藏字段值存在于请求参数映射中,FacesContext#isPostback()RestoreViewPhase#execute()期间评估为true,因此JSF将尝试实际上还原视图而不是创建一个新视图。但是,由于javax.faces.ViewState隐藏字段实际上是指上一个会话的旧视图状态(当前会话中不再存在),因此会抛出ViewExpiredException

这是一个不幸的问题。基本上,Servlet API和JSF API相互冲突。基本上有两种解决方案:

  1. 告诉容器不保存基于FORM的身份验证的POST数据。对于Tomcat和克隆,这需要将maxSavePostSize/conf/server.xml元素的-1属性设置为<Connector ... maxSavePostSize="-1">

    j_security_check

    对于Glassfish 3.x,抱歉我不知道!我在文档中偷看,但我没有立即看到任何接近的文件。

  2. 请勿使用{{1}}。使用编程身份验证代替真正的JSF表单和真正的辅助bean。这允许对处理成功登录进行更细粒度的控制。有关精心设计的代码段,请检查此答案的后半部分,从“更新”开始:<Connector>这不会继续使用已恢复的POST数据的POST请求,而只是在请求URI上发送真正的重定向。 / p>

  3. 它适用于ajax请求,因为登录页面是由OmniFaces的ajax响应指示的重定向打开的。因此,在基于FORM的身份验证检查被点击时,基本上没有任何需要保存的POST数据的方法。