如果JSF页面受j_security_check保护,则不会在ajax请求上引发ViewExpiredException



我有一个JSF页面,它不受j_security_check保护。我执行以下步骤:

  1. 在浏览器中打开JSF页面
  2. 重新启动服务器
  3. 单击JSF页面上的命令按钮来启动ajax调用

Firebug显示如预期的那样引发了ViewExpiredException

文章:

javax.faces.ViewState=8887124636062606698:-1513851009188353364

响应:

<partial-response>
<error>
<error-name>class javax.faces.application.ViewExpiredException</error-name>
<error-message>viewId:/viewer.xhtml - View /viewer.xhtml could not be restored.</error-message>
</error>
</partial-response>

然而,一旦我将页面配置为受j_security_check保护,并执行上面列出的相同步骤,奇怪的是(对我来说)ViewExpiredException不再升高。相反,回应只是一种新的观点状态。

文章:

javax.faces.ViewState=-4873187770744721574:8069938124611303615

响应:

<partial-response>
<changes>
<update id="javax.faces.ViewState">234065619769382809:-4498953143834600826</update>
</changes>
</partial-response>

有人能帮我弄清楚吗?我希望它会引发一个异常,这样我就可以处理该异常并显示一个错误页面。现在它只回复了一个新的ViewState,我的页面在没有任何视觉反馈的情况下被卡住了。

我能够重现您的问题。这里发生的情况是,容器调用一个RequestDispatcher#forward()到安全约束中指定的登录页。但是,如果登录页面本身也是JSF页面,那么FacesServlet也将在转发的请求中被调用。由于请求是转发的,因此只需在转发的资源(登录页面)上创建一个新视图。但是,由于这是一个ajax请求,并且没有render信息(整个POST请求基本上在安全检查转发过程中被丢弃),因此只返回视图状态。

请注意,如果登录页面不是JSF页面(例如JSP或纯HTML),那么ajax请求会将页面的整个HTML输出作为ajax响应返回,这是JSF-ajax无法解析的,并被解释为"空"响应。

不幸的是,它正在"按设计"工作。我怀疑JSF规范中对ajax请求的安全约束检查存在一些疏忽。原因毕竟是可以理解的,幸运的是很容易解决。只是,您实际上不想在这里显示错误页面,而是只想显示整个登录页面,就像在非ajax请求期间一样。您只需要检查当前请求是否是ajax请求并已转发到登录页面,然后您需要发送一个特殊的"重定向"ajax响应,以便更改整个视图。

您可以使用PhaseListener实现这一点,如下所示:

public class AjaxLoginListener implements PhaseListener {
@Override
public PhaseId getPhaseId() {
return PhaseId.RESTORE_VIEW;
}
@Override
public void beforePhase(PhaseEvent event) {
// NOOP.
}
@Override
public void afterPhase(PhaseEvent event) {
FacesContext context = event.getFacesContext();
HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest();
String originalURL = (String) request.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI);
String loginURL = request.getContextPath() + "/login.xhtml";
if (context.getPartialViewContext().isAjaxRequest()
&& originalURL != null
&& loginURL.equals(request.getRequestURI()))
{
try {
context.getExternalContext().invalidateSession();
context.getExternalContext().redirect(originalURL);
} catch (IOException e) {
throw new FacesException(e);
}
}
}
}

更新此解决方案是因为OmniFaces 1.2已内置到OmniPartialViewContext中。因此,如果你碰巧已经使用了OmniFaces,那么这个问题就完全透明地解决了,你不需要自定义的PhaseListener

上面的AjaxLoginListener解决方案适用于我。有趣的是,我们使用的是omnifaces 3.11.1,但OmniPartialViewContext在我的场景中不起作用。这是因为对loginViewId的检查与当前viewId不匹配,因为我的web.xml中有一个org.jsboss.weld.contexts.NonexistentConversationException的错误页。请注意,当为我触发AjaxLoginListener时,它会在调用context.getExternalContext().invalidateSession()时抛出异常;所以它从不调用重定向()。所以我不确定我的场景是否与这个线程中的原始场景完全相同。以下是我用来重新创建场景的步骤:

  1. 使用ajax命令按钮访问xhtml页面
  2. 等待会话超时
  3. 单击ajax命令按钮
  4. 用户被重定向到映射到web.xml中的NonexistentConversationException的错误页面
  5. 单击该页面上请求安全url的链接
  6. 系统显示登录页面-登录
  7. 单击指向xhtml页面的链接,该页面包含步骤1中的ajax命令按钮
  8. 系统显示包含NonexistentConversationException错误页面内容的部分响应

AjaxLoginListener是否可能因为映射到PhaseId而工作。RESTORE_VIEW,而OmniPartialViewContext被映射到PhaseId。RENDER_RESPONSE?

最新更新