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

时间:2012-09-19 23:10:35

标签: ajax jsf-2 j-security-check viewexpiredexception

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

  1. 在浏览器中打开JSF页面。
  2. 重新启动服务器。
  3. 单击JSF页面上的命令按钮以启动ajax调用。
  4. 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,我的页面卡住了没有任何视觉反馈。

1 个答案:

答案 0 :(得分:9)

我能够重现你的问题。这里发生的是容器调用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,则此问题已fully transparently已解决,您无需为此设置自定义PhaseListener