会话到期时的授权重定向不适用于提交 JSF 表单,页面保持不变

Posted

技术标签:

【中文标题】会话到期时的授权重定向不适用于提交 JSF 表单,页面保持不变【英文标题】:Authorization redirect on session expiration does not work on submitting a JSF form, page stays the same 【发布时间】:2013-01-12 20:49:30 【问题描述】:

我正在使用 JSF2。我已经实现了一个自定义面孔 servlet,如下所示:

public class MyFacesServletWrapper extends MyFacesServlet 
    // ...

我正在做一些授权检查并在用户未登录时发送重定向:

public void service(ServletRequest request, ServletResponse response) 
    HttpServletRequest req = (HttpServletRequest) request;
    HttpServletResponse res = (HttpServletResponse) response;

    if (...) 
        String loginURL = req.getContextPath() + "/LoginPage.faces";
        res.sendRedirect(loginURL);
    

这在用户尝试导航到另一个页面时有效。但是,当通过 JSF 命令链接/按钮提交 JSF 表单时,这不起作用。 sendRedirect() 行被命中并执行,没有抛出异常,但用户停留在同一页面。基本上,视觉上没有任何变化。

为什么这适用于页面导航,但不适用于表单提交?

【问题讨论】:

【参考方案1】:

您的具体问题很可能是因为您的 JSF 命令链接/按钮实际上正在发送一个 ajax 请求,而该请求又需要一个特殊的 XML 响应。如果您发送重定向作为对 ajax 请求的响应,那么它只会将 ajax 请求重新发送到该 URL。这反过来又没有反馈而失败,因为重定向 URL 返回整个 html 页面而不是特殊的 XML 响应。您实际上应该返回一个特殊的 XML 响应,其中指示 JSF ajax 引擎更改当前的window.location

但实际上你有更大的问题:使用错误的工具来完成这项工作。您应该使用servlet filter 来完成这项工作,而不是使用自己开发的 servlet,当然也不能取代负责所有 JSF 工作的 FacesServlet

假设您在请求/视图范围的 JSF 支持 bean 中执行登录,如下所示(如果您使用容器管理的身份验证,另请参见 Performing user authentication in Java EE / JSF using j_security_check 的第二个示例):

externalContext.getSessionMap().put("user", user);

那么这个过滤器的启动示例应该这样做:

@WebFilter("/*") // Or @WebFilter(servletNames="facesServlet")
public class AuthorizationFilter implements Filter 

    private static final String AJAX_REDIRECT_XML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
        + "<partial-response><redirect url=\"%s\"></redirect></partial-response>";

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws ServletException, IOException     
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        HttpSession session = request.getSession(false);
        String loginURL = request.getContextPath() + "/login.xhtml";

        boolean loggedIn = (session != null) && (session.getAttribute("user") != null);
        boolean loginRequest = request.getRequestURI().equals(loginURL);
        boolean resourceRequest = request.getRequestURI().startsWith(request.getContextPath() + ResourceHandler.RESOURCE_IDENTIFIER + "/");
        boolean ajaxRequest = "partial/ajax".equals(request.getHeader("Faces-Request"));

        if (loggedIn || loginRequest || resourceRequest)) 
            if (!resourceRequest)  // Prevent browser from caching restricted resources. See also https://***.com/q/4194207/157882
                response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1.
                response.setHeader("Pragma", "no-cache"); // HTTP 1.0.
                response.setDateHeader("Expires", 0); // Proxies.
            

            chain.doFilter(request, response); // So, just continue request.
        
        else if (ajaxRequest) 
            response.setContentType("text/xml");
            response.setCharacterEncoding("UTF-8");
            response.getWriter().printf(AJAX_REDIRECT_XML, loginURL); // So, return special XML response instructing JSF ajax to send a redirect.
        
        else 
            response.sendRedirect(loginURL); // So, just perform standard synchronous redirect.
        
    

    // ...

另见:

Using JSF 2.0 / Facelets, is there a way to attach a global listener to all AJAX calls? FullAjaxExceptionHandler does not show session expired error page on ajax button

【讨论】:

好的,我已经通过Filter 实现了它。但是,我仍然不明白else if("partial/ajax"...) 部分。您能否更详细地解释或提供一些链接? JSF ajax 请求需要特殊的 XML 响应。 HTTP 302 响应不会被 JSF ajax 引擎解释为窗口重定向,而是重定向到新的 XML 响应。但是,如果该重定向实际上返回一个 HTML 页面,它会失败并显示“无反馈”。您需要返回一个特殊的 XML 响应,告诉 JSF ajax 引擎在给定的 URL 上发送重定向。您可以通过检查提到的响应标头值来识别 JSF ajax 请求。在 JSF 方面,这通常是由ExternalContext#redirect() 处理的。但是在 JSF 上下文之外,您必须手动处理它。 这行request.getRequestURI().startsWith(request.getContextPath() + ResourceHandler.RESOURCE_IDENTIFIER); 应该处理css 文件吗?接缝好像没有。 仅当它们通过&lt;h:outputStylesheet&gt; 引用时。【参考方案2】:

FacesContext.getCurrentInstance().getExternalContext().redirect("newpage.xhtml");试试这个....代替 res.sendredirect(cpath)。

【讨论】:

谢谢。它不起作用:FacesContext.getCurrentInstance() 返回null。怎么办? 肯定不行。 FacesContext 仅在 FacesServlet 中可用,而不是本地 servlet。

以上是关于会话到期时的授权重定向不适用于提交 JSF 表单,页面保持不变的主要内容,如果未能解决你的问题,请参考以下文章

部分视图和视图的授权重定向

检查会话是不是存在 JSF

如何在 JSF 2 中处理会话过期和 ViewExpiredException?

JSF 2.2 - 文件上传不适用于 Ajax。表单的编码类型似乎不正确(仅通过 AJAX)

PrimeFaces 4.0/JSF 2.2.x 中的文件上传不适用于 AJAX - javax.servlet.ServletException:请求内容类型不是多部分/表单数据

将提交时的重力表单重定向到刚刚创建的帖子