会话过期时的授权重定向在提交JSF表单时不起作用,页面仍然保持不变。

9

我正在使用JSF2。我已经实现了一个自定义的Faces 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()被执行,没有抛出异常,但用户仍停留在同一页上。基本上,没有任何视觉变化。

为什么这个方法在页面导航时有效,但在表单提交时无效?

2个回答

19

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

但您实际上存在更大的问题:使用了错误的工具来完成任务。您应该使用servlet filter完成任务,而不是自制的Servlet,当然也不是取代FacesServlet的Servlet,后者负责所有JSF工作。

假设您正在执行以下请求/视图范围的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://dev59.com/FG855IYBdhLWcg3wsWr3
                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.
        }
    }

    // ...
}

另请参阅:


好的,我已经通过Filter实现了它。然而,我仍然不理解else if("partial/ajax"...)这部分。你能否详细解释一下或提供一些链接? - Danijel
1
JSF的ajax请求需要特殊的XML响应。HTTP 302响应不会被JSF ajax引擎解释为窗口重定向,而是作为指向新XML响应的重定向。但是,如果该重定向实际上返回的是HTML页面,则会失败并出现“无反馈”的错误。您需要返回一个特殊的XML响应,告诉JSF ajax引擎在给定的URL上发送重定向。通过检查所提到的响应头值,您可以识别JSF ajax请求。在JSF方面,这通常由ExternalContext#redirect()“在幕后”处理。但是,在JSF上下文之外,您必须手动处理它。 - BalusC
这行代码 request.getRequestURI().startsWith(request.getContextPath() + ResourceHandler.RESOURCE_IDENTIFIER); 是否可以处理 css 文件?看起来好像不行。 - Danijel
1
只有当它们通过<h:outputStylesheet>引用时。 - BalusC

0

尝试使用FacesContext.getCurrentInstance().getExternalContext().redirect("newpage.xhtml"); 代替 res.sendredirect(cpath)。


谢谢。它不起作用:FacesContext.getCurrentInstance() 返回 null。该怎么办? - Danijel
4
肯定不行。FacesContext 只能在 FacesServlet 中使用,而不能在自定义的 servlet 中使用。 - BalusC

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接