如果 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的主要内容,如果未能解决你的问题,请参考以下文章