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



我有一个在 Glassfish 上运行的 JSF 应用程序 [GlassFish Server Open Source Edition 3.1.2.2 (build 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。 如果会话过期并且单击了此按钮,则异常处理将按如下方式进行:显示登录页面;在(重新)输入登录凭据并登录时,将显示会话过期页面。 选择再次登录会让您重新开始营业。 我对此没有任何问题。 (请注意,最初我相信我在错误页面中特别引用了 ViewExpireException,而不是现在配置的 Throwable。 我描述的行为发生在使用 Throwable 和 ViewExpireException 时,需要注意的是,在 ViewExpireException 的情况下,我添加了一个额外的异常类型来满足 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 的控件时,ViewExpireException 处理工作正常(事实上,我更喜欢在处理未启用 AJAX 的控件时发生的序列: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

请注意,对于具有值"/faces/login.xhtml"的表单登录页面,无论是否正在使用OmniFaces,都会发生错误;我不认为这是一个OmniFaces的问题。 但是,我不明白 (a) 为什么会发生这种情况,或者 (b) 是否有一种方法可以维护我的现有配置(例如,保留我的 servlet 映射等)并使会话过期处理同时适用于启用 AJAX 和未启用 AJAX 的控件。

多用户.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>

viewExpire.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>

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

具体问题之所以引起,是因为默认情况下,普通容器会保存命中基于 FORM 的身份验证的 POST 请求的 POST 数据。当基于表单的身份验证成功时,将恢复此 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 是相互冲突的。基本上有2个解决方案:

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

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

    对于玻璃鱼3.x,对不起,我不知道!我在它的文档中偷看了一下,但我没有立即看到任何接近的东西。

  2. 不要使用j_security_check .请改用真正的 JSF 表单和真正的后备 Bean 进行编程身份验证。这允许对处理成功登录进行更精细的控制。有关详细的代码片段,请查看此答案的后半部分,从"更新"开始: 使用 j_security_check 在 Java EE/JSF 中执行用户身份验证 这不会使用还原的 POST 数据继续 POST 请求,而只是在请求 URI 上发送真正的重定向。

它适用于 ajax 请求,因为登录页面是按照 OmniFaces 的 ajax 响应的指示通过重定向打开的。因此,在进行基于表单的身份验证检查时,基本上没有任何需要保存的POST数据的方法。

最新更新