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

Posted

技术标签:

【中文标题】如果 JSF 页面受 j_security_check 保护,则不会在 ajax 请求上引发 ViewExpiredException【英文标题】:ViewExpiredException not thrown on ajax request if JSF page is protected by j_security_check 【发布时间】:2012-09-12 07:37:56 【问题描述】:

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

    在浏览器中打开 JSF 页面。 重新启动服务器。 单击 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,我的页面卡住了,没有任何视觉反馈。

【问题讨论】:

【参考方案1】:

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

【讨论】:

谢谢 BalusC。拯救了我的一天。 哦,还有一个问题,我想知道为什么会发生以下情况:如果我使用 context.getExternalContext().redirect(loginURL),它确实会将我重定向到登录页面。但是登录后,浏览器会显示一个以ViewState为内容的xml文件。 xml 文件与我在问题中发布的第二个 xml 文件完全相同。如果我使用 context.getExternalContext().redirect(homepageURL),一切正常。它会带我进入登录页面。登录后,将显示主页。 你是对的,这是另一个令人讨厌的问题:容器管理的身份验证会记住所有请求参数(包括无效的 javax.faces.ViewState 和另一个指示它是 ajax 请求的请求参数)并在之后重新传递它成功登录,这将导致 ViewExpiredException 错误页面作为 ajax 响应。这可以通过重定向到转发 URI 来避免,而转发 URI 又应该通过普通的 GET 请求而不是 ajax POST 请求再次重新触发安全检查。我已经相应地更新了答案。 @BalusC OmniPartialViewContext 对我没有任何作用。 Ajax 发布到安全页面会导致 403 错误(而不是 viewExpiredException),jsf ajax 处理程序会忽略该错误,因此不会发生任何事情。在 JSF 中处理过期视图时,似乎不可能在服务器端做任何事情。没有过滤器、错误处理程序或任何东西被调用。有什么建议吗? 感谢您的解决方案。需要检查 originalURL!=null 吗? request.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI) 对于我的 Web 应用程序始终为空【参考方案2】:

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

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

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

【讨论】:

以上是关于如果 JSF 页面受 j_security_check 保护,则不会在 ajax 请求上引发 ViewExpiredException的主要内容,如果未能解决你的问题,请参考以下文章

jsf spring安全访问被拒绝认证登录

意外错误转发或重定向到登录页面[重复]

在页面加载时调用 JSF 托管 bean 操作

javax.faces.FacesException:加载 JSF 页面时解码资源数据时出错

JSF:将页面动态包含到primefaces对话框中

如何在页面呈现期间测试是不是存在 JSF 导航案例