1

我有一个在 Glassfish [GlassFish Server Open Source Edition 3.1.2.2 (build 5) 上运行的 JSF 应用程序;Mojarra 2.1.6(快照 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 的 ap:commandButton。如果会话过期并单击此按钮,则异常处理如下:显示登录页面;在(重新)输入登录凭据并登录时,会显示会话过期页面。选择再次登录让您重新开始工作。我对此没有任何问题。(请注意,最初我认为我在错误页面中专门引用了 ViewExpiredException,而不是现在配置的 Throwable。我描述的行为在使用 Throwable 和使用 ViewExpiredException 时都会发生,但需要注意的是ViewExpiredException 的情况下,我添加了一个额外的异常类型来满足 OmniFaces 要求)。

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

当 p:commandButton 使用 AJAX、使用 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>

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

4

1 回答 1

1

具体问题是因为默认情况下,一般容器保存了基于 FORM 身份验证的 POST 请求的 POST 数据。当基于 FORM 的身份验证成功时,将恢复此 POST 数据(所有 POST 请求参数)。JSFjavax.faces.ViewState隐藏字段值也出现在 POST 数据中。

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

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

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

    <Connector ... maxSavePostSize="-1">
    

    对于 Glassfish 3.x,对不起,我不知道!我在它的文档中四处看了看,但我没有立即看到任何接近的东西。

  2. 不要使用j_security_check. 使用编程式身份验证代替真正的 JSF 表单和真正的支持 bean。这允许对处理成功登录进行更细粒度的控制。对于详细的代码片段,请检查此答案的第二部分,从“更新”开始:使用 j_security_check 在 Java EE / JSF 中执行用户身份验证这不会继续使用恢复的 POST 数据的 POST 请求,而只是发送一个真正的重定向请求 URI。

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

于 2013-11-06T21:02:00.607 回答