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

Ran*_*ang 6 ajax j-security-check jsf-2 viewexpiredexception

我有一个不受保护的JSF页面j_security_check.我执行以下步骤:

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

Firebug显示a ViewExpiredException正如预期的那样被提升.

帖子:

javax.faces.ViewState=8887124636062606698:-1513851009188353364
Run Code Online (Sandbox Code Playgroud)

响应:

<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>
Run Code Online (Sandbox Code Playgroud)

但是,一旦我将页面配置为受保护j_security_check并执行上面列出的相同步骤,奇怪的是(对我而言)ViewExpiredException不再引发.相反,响应只是一种新的视图状态.

帖子:

javax.faces.ViewState=-4873187770744721574:8069938124611303615
Run Code Online (Sandbox Code Playgroud)

响应:

<partial-response>
<changes>
<update id="javax.faces.ViewState">234065619769382809:-4498953143834600826</update>
</changes>
</partial-response>
Run Code Online (Sandbox Code Playgroud)

有人可以帮我解决这个问题吗?我希望它引发异常,以便我可以处理该异常并显示错误页面.现在它只是响应一个新的ViewState,我的页面卡住了没有任何视觉反馈.

Bal*_*usC 10

我能够重现你的问题.这里发生的是容器调用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);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

更新此解决方案是因为OmniFaces 1.2已内置于OmniPartialViewContext.因此,如果您恰好使用OmniFaces,那么这个问题就完全透明地解决了,您不需要PhaseListener为此定制.