我有一个在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>
任何人都可以提供帮助,理解为什么会发生这种情况,并了解解决问题的方法,我们将不胜感激。感谢。
答案 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相互冲突。基本上有两种解决方案:
告诉容器不保存基于FORM的身份验证的POST数据。对于Tomcat和克隆,这需要将maxSavePostSize
中/conf/server.xml
元素的-1
属性设置为<Connector ... maxSavePostSize="-1">
。
j_security_check
对于Glassfish 3.x,抱歉我不知道!我在文档中偷看,但我没有立即看到任何接近的文件。
请勿使用{{1}}。使用编程身份验证代替真正的JSF表单和真正的辅助bean。这允许对处理成功登录进行更细粒度的控制。有关精心设计的代码段,请检查此答案的后半部分,从“更新”开始:<Connector>
这不会继续使用已恢复的POST数据的POST请求,而只是在请求URI上发送真正的重定向。 / p>
它适用于ajax请求,因为登录页面是由OmniFaces的ajax响应指示的重定向打开的。因此,在基于FORM的身份验证检查被点击时,基本上没有任何需要保存的POST数据的方法。