Authorization Redirect on Session Expiration Does Not Work on Submitting a Jsf Form, Page Stays the Same

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

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://stackoverflow.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.
}
}

// ...
}

See also:

  • 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

redirect not working when is session invalid

This my own answer to my question to all of them who are interested in.
The problem was the ajax request of course and the solution is simple:

I just needed a check if it is a Ajax request like:

final String facesRequest = request.getHeader("Faces-Request");
if (facesRequest != null && facesRequest.equals("partial/ajax")) {
//it is
}

and then return the following xml part:

final StringBuilder sb = new StringBuilder();
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?><partial-response><redirect url=\"").append(req.getContextPath() + sessionExpiredPage).append("\"></redirect></partial-response>");
res.setHeader("Cache-Control", "no-cache");
res.setCharacterEncoding("UTF-8");
res.setContentType("text/xml");
final PrintWriter writer = res.getWriter();
writer.println(sb.toString());
eriter.flush();

Thats it and it works like a charm.

FullAjaxExceptionHandler does not show session expired error page on ajax button

You're sending a synchronous redirect as a response to the ajax request (a HTTP 302 response using e.g. response.sendRedirect()). This is not right. The JavaScript ajax engine treats the 302 response as a new destination to re-send the ajax request to. However, that in turn returns a plain vanilla HTML page instead of a XML document with instructions which parts of the page to update. This is confusing and thus the redirected response is altogether ignored. That explains precisely the symptoms you're facing.

The very same problem is also asked and answered in the following closely related questions:

  • FullAjaxExceptionHandler does not redirect to error page after invalidated session
  • How to move user to timeout page when session expires, if user click on browser back button
  • GET request for redirect initiated by browser but not successful
  • Authorization redirect on session expiration does not work on submitting a JSF form, page stays the same
  • JSF Filter not redirecting After Initial Redirect

Basically, you need to instruct Spring Security in some way to perform the following conditional check:

if ("partial/ajax".equals(request.getHeader("Faces-Request"))) {
// JSF ajax request. Return special XML response which instructs JavaScript that it should in turn perform a redirect.
response.setContentType("text/xml");
response.getWriter()
.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>")
.printf("<partial-response><redirect url=\"%s\"></redirect></partial-response>", loginURL);
} else {
// Normal request. Perform redirect as usual.
response.sendRedirect(loginURL);
}

I'm however no Spring user and I'm not interested to use it, and am therefore not able to give a more detailed answer how to perform this check in Spring Security. I can however tell that Apache Shiro has exactly the same problem which is explained and solved in this blog article: Make Shiro JSF Ajax Aware.

Redirect on ViewExpiredException in AJAX request in JSF 2

I do it like this in the webfilter, and it works fine:

    // Check if user logged in, if not redirect to login.xhtml
if (loginBean == null || !((LoginBean) loginBean).isLoggedIn()) {
boolean isAjax = "XMLHttpRequest".equals(req.getHeader("X-Requested-With"));

if (!isAjax) {
res.sendRedirect(req.getContextPath() + "/login.xhtml");
} else {
// Redirecting an ajax request has to be done in the following way:
// http://javaevangelist.blogspot.com/2013/01/jsf-2x-tip-of-day-ajax-redirection-from.html
String redirectURL = res.encodeRedirectURL(req.getContextPath() + "/login.xhtml");
StringBuilder sb = new StringBuilder();
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?><partial-response><redirect url=\"").append(redirectURL).append("\"></redirect></partial-response>");
res.setCharacterEncoding("UTF-8");
res.setContentType("text/xml");
PrintWriter pw = response.getWriter();
pw.println(sb.toString());
pw.flush();
}
} else {
// Let chain of filters continue;
chain.doFilter(request, response);
}

Inspiration

Redirect to same page from webfilter independent from called URL

Redirect to a domain-relative URL. In other words, always start the redirect URL with /, including the context path. If your actual concern is the dynamicness of the context path /App, just obtain it programmatically by HttpServletRequest#getContextPath().

response.sendRedirect(request.getContextPath() + "/login.jsf");

Do note that this was already covered by the filter example I gave you in your previous question.

Webfilter login mechanism takes always two attempts

It appears that you got the responsibility of the filter wrong. Its responsibility is not to perform the actual login (or logout). Its responsibility is to restrict access to the requested resource based on the logged-in user.

You should perform the actual login (and logout) in the action method of the request/view scoped backing bean associated with the login form. After a successful login/logout you should redirect to the target page. The filter should just check if the currently logged-in user is allowed to access the requested resource. If it is, then continue filter chain. If it is not, then redirect to login page or send 401/403.

Note that your filter doesn't cover JSF resource or ajax requests. In this answer you can find a complete example of a JSF aware authentication filter: Authorization redirect on session expiration does not work on submitting a JSF form, page stays the same.



Related Topics



Leave a reply



Submit