7

我有一个不受j_security_check. 我执行以下步骤:

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

Firebug 显示 aViewExpiredException如预期的那样被引发。

邮政:

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,我的页面卡住了,没有任何视觉反馈。

4

2 回答 2

13

我能够重现您的问题。这里发生的是容器调用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为此定制。

于 2012-09-20T03:29:34.077 回答
0

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

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

AjaxLoginListener 是否可能正在工作,因为它映射到 PhaseId.RESTORE_VIEW 而 OmniPartialViewContext 映射到 PhaseId.RENDER_RESPONSE?

于 2021-09-29T14:39:44.373 回答