Authorization redirect on session expiration does not work on submitting a JSF form, page stays the same
Asked Answered
W

2

9

I am using JSF2. I have implemented a custom faces servlet like so:

public class MyFacesServletWrapper extends MyFacesServlet {
    // ...
}

wherein I'm doing some authorization checks and sending a redirect when the user is not logged in:

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);
    }
}

This works when the user tries to navigate to another page. However, this does not work when a JSF form is submitted by a JSF command link/button. The line sendRedirect() line is hit and executed, no exception is been thrown, but the user stays at the same page. Basically, there's no visual change at all.

Why does this work on page navigation, but not on form submit?

Wessel answered 29/1, 2013 at 10:5 Comment(0)
L
19

Your concrete problem is most likely caused because your JSF command link/button is actually sending an ajax request which in turn expects a special XML response. If you're sending a redirect as response to an ajax request, then it would just re-send the ajax request to that URL. This in turn fails without feedback because the redirect URL returns a whole HTML page instead of a special XML response. You should actually be returning a special XML response wherein the JSF ajax engine is been instructed to change the current window.location.

But you've actually bigger problems: using the wrong tool for the job. You should use a servlet filter for the job, not a homegrown servlet and for sure not one which supplants the FacesServlet who is the responsible for all the JSF works.

Assuming that you're performing the login in a request/view scoped JSF backing bean as follows (if you're using container managed authentication, see also 2nd example of Performing user authentication in Java EE / JSF using j_security_check):

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

Then this kickoff example of a filter should do:

@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://mcmap.net/q/18174/-prevent-user-from-seeing-previously-visited-secured-page-after-logout/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.
        }
    }

    // ...
}

See also:

Leucopenia answered 29/1, 2013 at 11:39 Comment(4)
OK, I've implemented it via Filter. However, I still don't understand the else if("partial/ajax"...) part. Could you explain in more detail or provide some links?Wessel
A JSF ajax request requires a special XML response. A HTTP 302 response is not interpreted as a window redirect by JSF ajax engine, but as a redirect to a new XML response. However if that redirect actually returns a HTML page, it fails with "no feedback". You need to return a special XML response which tells JSF ajax engine to send a redirect on the given URL. You can recognize JSF ajax requests by checking the mentioned response header value. In JSF side, this is normally "under the covers" handled by ExternalContext#redirect(). But outside JSF context, you have to deal with it manually.Leucopenia
Should this line request.getRequestURI().startsWith(request.getContextPath() + ResourceHandler.RESOURCE_IDENTIFIER); handle css files? Seams like it doesn't.Wessel
Only if they're referenced via <h:outputStylesheet>.Leucopenia
B
0

FacesContext.getCurrentInstance().getExternalContext().redirect("newpage.xhtml"); try this.... in place of res.sendredirect(cpath).

Biotic answered 29/1, 2013 at 10:11 Comment(2)
Thanks. It does not work: FacesContext.getCurrentInstance() returns null. What to do?Wessel
Surely it doesn't work. The FacesContext is only available in a FacesServlet, not a homegrown servlet.Leucopenia

© 2022 - 2024 — McMap. All rights reserved.