Is there any easy way to preprocess and redirect GET requests? - jsf

I'm looking for a best practise answer. I want to do some preprocessing for GET requests. So e.g. if the user is not allowed to see the page, redirect him to another page. But I don't want to use normal servlet filter, because I would like to express this behavior in the faces-config.xml. Is this possible and how is that called, how can it be done?
Can I define some Filter bean that also returns a String telling the faces-config.xml where to go next?
I googled for this but only hit on the normal filters. If I use filters, can a #WebFilter be a #ManagedBean at the same time? Or is that bad style?

If you're homegrowing HTTP request authentication on top of JSF, then a servlet filter is really the best approach. JSF is "just" a MVC framework and nothing in the JSF API is been specified to filter incoming HTTP requests to check user authentication. On normal GET requests, a JSF managed bean is usually only constructed when the HTTP response is about to be created and sent, or maybe already is been committed. This is not controllable from inside the managed bean. If the response is already been committed, you would not be able anymore to change (redirect) it. Authentication and changing the request/response really needs to be done far before the response is about to be sent.
If you were not homegrowing authentication, then you could have used the Java EE provided container managed authentication for this which is to be declared by <security-constraint> entries in web.xml. Note that this is also decoupled from JSF, but it at least saves you from homegrowing a servlet filter and a managed bean.
The general approach is to group the restricted pages behind a certain URL pattern like /app/*, /private/*, /secured/*, etc and to take the advantage of the fact that JSF stores session scoped beans as HttpSession attributes. Imagine that you've a JSF session scoped managed bean UserManager which holds the logged-in user, then you could check for it as follows:
#WebFilter(urlPatterns={"/app/*"})
public class AuthenticationFilter implements Filter {
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
HttpSession session = request.getSession(false);
UserManager userManager = (session != null) ? (UserManager) session.getAttribute("userManager") : null;
if (userManager == null || !userManager.isLoggedIn()) {
response.sendRedirect(request.getContextPath() + "/login.xhtml"); // No logged-in user found, so redirect to login page.
} else {
chain.doFilter(req, res); // Logged-in user found, so just continue request.
}
}
// ...
}
If you're using JSF 2.2+, there's another way to control the response right before it is been sent. You can make use of the <f:viewAction>. Put the following somewhere in your view:
<f:metadata>
<f:viewAction action="#{authenticator.check}" />
</f:metadata>
with
#Named
#RequestScoped // Scope doesn't matter actually. The listener will always be called on every request.
public class Authenticator {
public String check() {
if (authenticated) {
return null;
}
else {
return "login?faces-redirect=true";
}
}
// ...
}
This is guaranteed to be fired before the response is to be rendered. Otherwise when you do the job in e.g. #PostConstruct, then you may risk java.lang.IllegalStateException: response already committed when the bean is created for the first time when the response has already partially been rendered (and committed).
I only wouldn't consider it to be a "best" practice when it comes to handling HTTP authentication. It makes it too tight coupled into JSF. You should really keep using a servlet filter. But for other purposes, it may be fine.
See also:
When to use f:viewAction / preRenderView versus PostConstruct?
What can <f:metadata>, <f:viewParam> and <f:viewAction> be used for?
Limitations of using a PhaseListener instead of a Servlet Filter for authorization

Related

JSF: Redirection [duplicate]

I have a JSF page which posts data to an external page.
The data is loaded from a JSF managed bean which generates a unique ID in the post data.
I have an issue where a user clicks on a checkout button then navigates back to the same page and presses the checkout button again. The post data has not updated. Moreover, the bean is not invoked at all. Is there anyway to force JSF to reload the page and the form data?
<form action="#{checkoutBean.externalUrl}" method="post"
id="payForm" name="payForm">
<input type="hidden" value="#{checkoutBean.uniqueID}" />
<input type="submit" value="Proceed to Checkout" />
</form>
That page is likely being loaded from browser cache. This is essentially harmless, but indeed confusing to the enduser, because s/he incorrectly thinks that it's really coming from the server. You can easily confirm this by looking at the HTTP traffic monitor in browser's web developer toolset (press F12 in Chrome/FireFox23+/IE9+ and check "Network" section).
You basically need to tell the browser to not cache (dynamic) JSF pages. This way the browser will actually request the server for the page (and hereby triggering proper creation/initialization of managed beans and so forth) instead of showing the previously requested one from its cache.
Generally, this is to be done with a simple servlet filter like follows:
#WebFilter("/app/*")
public class NoCacheFilter implements Filter {
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (!request.getRequestURI().startsWith(request.getContextPath() + ResourceHandler.RESOURCE_IDENTIFIER)) { // Skip JSF resources (CSS/JS/Images/etc)
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(req, res);
}
// ...
}
Where /app/* is the example URL pattern on which you'd like to turn off the browser cache. You can if necessary map it on /*, *.xhtml or even on servletNames={"Faces Servlet"}.
If you happen to use JSF utility library OmniFaces, then you can use its builtin CacheControlFilter by just adding the following entry to web.xml (which demonstrates a direct mapping on FacesServlet, meaning that every dynamic JSF page won't be cached):
<filter>
<filter-name>noCache</filter-name>
<filter-class>org.omnifaces.filter.CacheControlFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>noCache</filter-name>
<servlet-name>facesServlet</servlet-name>
</filter-mapping>
See also the showcase.
I found a solution that works for JSF without having to create a servlet-filter. Just put the line below to your .xhtml page.
<f:event type="preRenderView" listener="#{facesContext.externalContext.response.setHeader('Cache-Control', 'no-cache, no-store')}" />

JSF 2.0 Multiple requests generated per page

I have implemented a Filter for checking if a user is logged in or not by checking the session for a #SessionScoped bean. When I started testing it, I however, noticed that whenever I accessed one of my pages the Filter would be invoked multiple times.
I have figured out that I needed to ignore AJAX requests which reduced the number of times my filter would be invoked but the number of requests triggered each time I loaded the page was still more than one.
By trial and error, I have figured out that the requests would be generated by the following XHTML tags (both embedded in the <h:body> tag):
<h:outputStylesheet name="styles/userbar.css" target="head"/>
<o:commandScript name="updateMessages" render="custom_messages"/>
The second tag being part of OmniFaces library.
Any ideas why I get multiple requests or maybe if there is a way to ignore the requests generated by these tags?
Any help would be appreciated.
That can happen if you mapped the filter on a generic URL pattern like #WebFilter("/*"), or directly on the faces servlet like #WebFilter(servletNames="facesServlet"). The requests you're referring to are just coming from (auto-included) CSS/JS/image resources. If you track the browser's builtin HTTP traffic monitor (press F12, Network) or debug the request URI in filter, then that should have become clear quickly.
As to covering JSF resource requests, if changing the filter to listen on a more specific URL pattern like #WebFilter("/app/*") is not possible for some reason, then you'd need to add an additional check on the request URI. Given that you're using OmniFaces, you can use the Servlets utility class to check in a filter if the current request is a JSF ajax request or a JSF resource request:
#WebFilter("/*")
public class YourFilter extends HttpFilter {
#Override
public void doFilter(HttpServletRequest request, HttpServletResponse response, HttpSession session, FilterChain chain) throws IOException, ServletException {
if (Servlets.isFacesAjaxRequest(request) || Servlets.isFacesResourceRequest(request)) {
chain.doFilter(request, response);
return;
}
// ...
}
}
See also:
Authorization redirect on session expiration does not work on submitting a JSF form, page stays the same (contains a "plain vanilla" Servlet example for the case you aren't using OmniFaces)

Access session scoped JSF managed bean in web filter

I have SessionScoped bean called userSession to keep track of the user ( username, ifLogged, etc). I want to filter some pages and therefore I need to access the bean from the webFilter I created. How do I do that? I looks like its even impossible to import the bean to be potenitally visible.
Under the covers, JSF stores session scoped managed beans as an attribute of the HttpSession with the managed bean name as key.
So, provided that you've a #ManagedBean #SessionScoped public class User {}, just this should do inside the doFilter() method:
HttpSession session = ((HttpServletRequest) request).getSession(false);
User user = (session != null) ? (User) session.getAttribute("user") : null;
if (user != null && user.isLoggedIn()) {
// Logged in.
}
Or, if you're actually using CDI instead of JSF to manage beans, then just use #Inject directly in the filter.
See also:
Get JSF managed bean by name in any Servlet related class
Prevent accessing restricted page without login in Jsf2
As an alternative you can use CDI-beans and inject your sessionbean normally.

Access ViewScoped ManagedBean from Servlet

Background information: I have a file upload applet in my jsf page. This applet expects an adress where it can send it's POST request. (I can't edit this post request to add more fields or something). The post method of my servlet then stores the file. This job can't be done by a managed bean because the servlet has to be annotated with #MultiPartConfig and I can't add this annotation to the jsf managed bean. In order to force the upload applet to use the same session I added an URL attribute named jsessionId to the post request according to this post. In my servlet I am now able to access session scoped beans.
Now I have a ViewScoped bean where I store some form input data which I want to use in the servlet, since adding those inputs to the post request doesn't work (Applet is a third party project (JUploadApplet) and for some reason it doesn't work to add additional form data).
Now is it possible to access the ViewScoped bean from within the servlet ? If I change the scope into SessionScope I am able to process the input but with ViewScoped I get a NullPointerException if I try to access the bean like this :
UploadBean uploadBean = (UploadBean)request.getSession().getAttribute("uploadBean");
This is not possible. Your best bet is to let the view scoped bean generate an unique key, store itself in the session scope by that key and pass that key as additional parameter to the applet and finally let the servlet access the session attribute by that key.
E.g.
private String sessionKey;
#PostConstruct
public void init() {
sessionKey = UUID.randomUUID().toString();
FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put(sessionKey, this);
}
#PreDestroy
public void destroy() {
FacesContext.getCurrentInstance().getExternalContext().getSessionMap().remove(sessionKey);
}
Let the applet pass the sessionKey as request parameter to the servlet, so that the servlet can do
String sessionKey = request.getParameter("sessionKey");
Bean bean = (Bean) request.getSession().getAttribute(sessionKey);
// ...
Note that instead of the bean itself, you can also just store an arbitrary bean/valueobject/etc.

What is the relation between session scoped beans and HttpServletRequest?

While trying to figure out how to implement a login filter for a JSF app I saw these 2 lines of code that I didn't understand that much :
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
HttpServletRequest req = (HttpServletRequest) request;
LoginBean login = (LoginBean) req.getSession().getAttribute("login");
}
Assuming that LoginBean class is a session scoped bean named "login" , as I noticed the bean is an attribute for the request , what is the relation between them ? are all session scoped bean saved as "attributes" in request sessions ?
are all session scoped bean saved as "attributes" in request sessions ?
That's correct. JSF is just a MVC framework which is built on top of the bare Servlet API, not an entirely standalone framework which can run without the Servlet API. Even more, the JSF core controller FacesServlet is a fullworthy Servlet, so it definitely requires a servlet container to run. The concept "session" is in the Servlet API provided by HttpSession, so it would make fully sense to store JSF session scoped beans in there instead of reinventing it.
Note that JSF request scoped beans are stored as HttpServletRequest attributes and that JSF application scoped beans are stored as ServletContext attributes.
See also:
Get JSF managed bean by name in any Servlet related class
How do servlets work? Instantiation, sessions, shared variables and multithreading

Resources