browser back + viewscope beans - jsf-2

What the problem is :
What happens when clicking on the browser back button --> opens up a page whose viewscoped-managedbean is already destroyed --> submit a request from a commandButton from that page with grid-record-selections ?
What i expect :
The associated viewscope-managebean is re-created, receives the grid-record-selections, and deal with them as if the browser back button is never involved.
What i experience :
The associated viewscope-managebean is NOT re-created, doesnt receive the grid-record-selections. Have to reenter the URL, or F5 after clicking on the browser-back button for it to work properly again.
So here's the success scenario, all beans are viewscoped beans :
GET page1.xhtml --> page1Bean created, querying data, etc in #PostConstruct
check/select several records from a datatable, click on process button
page1Bean's process method stores the selected records in the flash object, and redirect to the page2.xhtml
page1Bean destroyed, page2Bean created, and in preRenderView listener method, fetches the selected records from the flash object, and deal with them
click the "go to main page" commandButton to redirect to page1.xhtml, and page2Bean destroyed, page1Bean created again
loop from no 2 - 5 is still doable
Now, this is the errornous scenario involving the browser back button (different stuffs happening starting from #6) :
GET page1.xhtml --> page1Bean created, querying data, etc in #PostConstruct
check/select several records from a datatable, click on process button
page1Bean's process method stores the selected records in the flash object, and redirect to the page2.xhtml
page1Bean destroyed, page2Bean created, and in preRenderView listener method, fetches the selected records from the flash object, and deal with them
click the browser back button page2Bean is not destroyed, page1Bean is not created
check/select several records from a datatable, click on process button
the page1Bean method executes (strange, because the page1Bean should've been destroyed), but cannot see the record-selections made, and redirect to page2.xhtml
page1Bean is not destroyed (no logging output), page2Bean is not created (since it's not been destroyed), executes the preRenderView listener as usual, but this time, no selected records in the flash object
Is it possible to have the normal experience (as if without the browser back button) with viewscope-beans with the browser back button ?
Here's my dependency :
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.faces</artifactId>
<version>2.1.3</version>
<scope>compile</scope>
</dependency>
Please share your ideas !

The browser seems to have served the page from its cache instead of sending a fullworthy HTTP GET request to the server, while you have JSF state saving method set to server (which is the default).
There are 2 ways to solve this problem:
Tell the browser to not cache the dynamic JSF pages. You can do this with help of a filter.
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
if (!req.getRequestURI().startsWith(req.getContextPath() + ResourceHandler.RESOURCE_IDENTIFIER)) { // Skip JSF resources (CSS/JS/Images/etc)
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1.
res.setHeader("Pragma", "no-cache"); // HTTP 1.0.
res.setDateHeader("Expires", 0); // Proxies.
}
chain.doFilter(request, response);
}
Map the filter on the FacesServlet or its same URL-pattern.
Set the JSF state saving method to client, so that the entire view state is stored in a hidden field of the form instead of in the session in the server side.
<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>client</param-value>
</context-param>
The filter way is preferable.

The disadvantage of disabling the browser cache of a page is that the user will see an browsers error page if he use browser back to navigate to previous page.
So another solutions is to identify if the page comes from the server or from the browser cache using javascript:
First create a simple backing bean which serves a unique id (in my case current system time):
#Named("browserCacheController")
#RequestScoped
public class BrowserCacheController implements Serializable {
private static final long serialVersionUID = 1L;
/**
* Returns a unique increasing id for each request
* #return
*/
public long getCacheID() {
return System.currentTimeMillis();
}
}
So now you can test if a page is served from the server or browser and redirecting the user if the current page comes from browser cache. See the following javascript code placed into a jsf page which should not be cached by browser:
<script type="text/javascript">
// check for latestCacheID
if (!isValidCacheID(#{browserCacheController.cacheID})) {
//redirect to some page
document.location="#{facesContext.externalContext.requestContextPath}/index.jsf";
}
// test cacheID if it comes from the server....
function isValidCacheID(currentCacheID) {
if (!('localStorage' in window && window['localStorage'] !== null))
return true; // old browsers not supported
var latestCacheID=localStorage.getItem("org.imixs.latestCacheID");
if (latestCacheID!=null && currentCacheID<=latestCacheID) {
return false; // this was a cached browser page!
}
// set new id
localStorage.setItem("org.imixs.latestCacheID", currentCacheID);
return true;
}
</script>
The script can also be placed into facelet to make the jsf code more clean.

Related

Primefaces DialogFramework - How to show a dialog located in WEB-INF?

I am using Primefaces DialogFramework with
Primefaces 5.0
Mojarra 2.1.27
Glassfish 3.1.2.2 Build 5
My problem is, that if the user knows the location of my dialog, he is able to access it directly via the URL. I do not want that to be possible, so I thought it would be able to put the dialog in WEB-INF folder of my web-app, but now, if I want to open the dialog, I get a FileNotFound-Exception.
If my dialog is located in some regular folder, it works fine
RequestContext.getCurrentInstance().openDialog("/myfolder/mydialog");
// this works as expected
but if it is located in WEB-INF, it does not work any longer
RequestContext.getCurrentInstance().openDialog("/WEB-INF/mydialog",options,null);
// this is causing a fileNotFoundException
I also tried to set up a navigation rule for this in faces-config but again with no success
<navigation-case>
<from-outcome>mydialog</from-outcome>
<to-view-id>/WEB-INF/mydialog.xhtml</to-view-id>
<redirect />
</navigation-case>
How may I open dialogs located in WEB-INF folder, or is it not possible at all?
Thanks in advance
Unfortunately, putting PrimeFaces Dialog Framework dialogs in /WEB-INF in order to prevent direct access is indeed not going to work. The dialogs are loaded entirely client side. On the POST request which opens the dialog, JSF/PrimeFaces returns an oncomplete script with the (public!) URL of the dialog to JavaScript/jQuery, which in turn shows a basic dialog template with an <iframe> whose URL is set to the dialog URL, which in turn loads the content. In effects, 2 requests are being sent, the first to get the dialog's URL and the second to get the dialog's content based on that URL in the <iframe>.
There's no way to keep the dialog in /WEB-INF without falling back to the "traditional" dialog approach via <p:dialog> and conditional display via JS/CSS. There's also no way in the server side to verify based on some headers if the request is coming from an <iframe>, so that all others could simply be blocked. Your closest bet is the referer header, but this can be spoofed.
One way to minimize abuse is checking the presence of pfdlgcid request parameter (identified by Constants.DIALOG_FRAMEWORK.CONVERSATION_PARAM) when a dialog is being requested. PrimeFaces namely appends this request parameter representing "conversation ID" to the dialog URL. Presuming that all dialogs are stored in a folder /dialogs, then you could do the job with a simple servlet filter. Here's a kickoff example which sends a HTTP 400 error when /dialogs/* is being requested without the pfdlgcid request parameter.
#WebFilter("/dialogs/*")
public class DialogFilter implements Filter {
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String id = request.getParameter(Constants.DIALOG_FRAMEWORK.CONVERSATION_PARAM);
if (id != null) {
chain.doFilter(req, res); // Okay, just continue request.
}
else {
response.sendError(HttpServletResponse.SC_BAD_REQUEST); // 400 error.
}
}
// ...
}
However, the abuser might not be that stupid and discover the pfdlgcid request parameter during the normal flow and still be able to open the dialog individually when supplying that parameter, even with a random value. I thought of comparing the actual pfdlgcid value to the known ones. I checked the PrimeFaces DialogNavigationHandler source code, but unfortunately, PrimeFaces doesn't store this value anywhere in the session. You'd need to provide a custom DialogNavigationHandler implementation wherein you store the pfdlgcid value in the session map which in turn is also compared in the servlet filter.
First add the following method to the DialogFilter:
public static Set<String> getIds(HttpServletRequest request) {
HttpSession session = request.getSession();
Set<String> ids = (Set<String>) session.getAttribute(getClass().getName());
if (ids == null) {
ids = new HashSet<>();
session.setAttribute(getClass().getName(), ids);
}
return ids;
}
Then copypaste the PrimeFaces DialogNavigationHandler source code into your own package and add the following line after line 62:
DialogFilter.getIds((HttpServletRequest) context.getExternalContext().getRequest()).add(pfdlgcid);
Replace the <navigation-handler> in faces-config.xml with the customized one.
Finally, alter the if condition in the DialogFilter#doFilter() method as follows:
if (getIds(request).contains(id)) {
// ...
}
Now, this prevents the abuser from attempting to open the dialog with a random ID. This however doesn't prevent the abuser from attempting to open the dialog by copypasting the exact <iframe> URL immediately after opening it. Given the way how the PrimeFaces dialog framework works, there's no way to prevent that. You could at most remove the pfdlgcid value from the session when the dialog is about to returns to the parent. However, when the dialog is closed by pure JS means, then this is also bypassed.
All in all, if you really, really, want to avoid the enduser being able to open the dialog individually, then you can't go around the "traditional" <p:dialog> approach.

How to redirect already authenticated user from login page to home page

I'm developing JSF application with Apache Shiro. I autenticate the user with Shiro and redirect her to home page there is no problem with that. After the authentication when I try to access login page, it doesn't redirect me the homepage. I can login again even when there is already loggedin user. I'm doing Programmatic Login as BalusC mentioned in his blog post.
[main]
credentialsMatcher = org.apache.shiro.authc.credential.PasswordMatcher
myRealm = com.example.security.myRealm
myRealm.credentialsMatcher = $credentialsMatcher
securityManager.realms = $myRealm
user = com.example.web.filter.FacesAjaxAwareUserFilter
user.loginUrl = /login.xhtml
[urls]
/login.xhtml = user
This filter is written from the blog post.
public class FacesAjaxAwareUserFilter extends UserFilter {
private static final String FACES_REDIRECT_XML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<partial-response><redirect url=\"%s\"></redirect></partial-response>";
#Override
protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
HttpServletRequest req = (HttpServletRequest) request;
if ("partial/ajax".equals(req.getHeader("Faces-Request"))) {
response.setContentType("text/xml");
response.setCharacterEncoding("UTF-8");
response.getWriter().printf(FACES_REDIRECT_XML, req.getContextPath() + getLoginUrl());
}
else {
super.redirectToLogin(request, response);
}
}
}
What is the problem and how can I redirect the user if she is already authenticated?
EDIT: For now I'm using PostConstruct annotation to redirect if the user is already authenticated. I'm open to any good solution.
After the authentication when I try to access login page, it doesn't redirect me the homepage. I can login again even when there is already loggedin user
Neither Shiro nor the custom Shiro user filter are intented to prevent that. Shiro doesn't have builtin facilities for this. The custom Shiro user filter runs only when an unauthenticated user is found, not when an already authenticated user is found.
Preventing an authenticated user from accessing the login page directly is your own responsibility. Depending on business requirements you can do the following:
Just allow it. Perhaps the user just want to switch logins. You could if necessary conditionally show a message like:
<ui:fragment rendered="#{not empty request.remoteUser}">
You are already logged-in as #{request.remoteUser}.
By logging in as another user, you will be logged out.
</ui:fragment>
<h:form id="login">
...
</h:form>
Don't allow it, but stay in the same page. Conditionally hide the login form and show a message like:
<ui:fragment rendered="#{not empty request.remoteUser}">
Hey, how did you end up here?
You are already logged-in as #{request.remoteUser}!
</ui:fragment>
<h:form id="login" rendered="#{empty request.remoteUser}">
...
</h:form>
And, of course, make sure that your web application doesn't have anywhere a login link when the user is already logged in.
Don't allow it and redirect to the desired target page. This can in turn be done in several ways. Most clean approach is using a servlet filter.
#WebFilter(urlPatterns = "/login.xhtml")
public class LoginPageFilter 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.getRemoteUser() != null) {
response.sendRedirect(request.getContextPath() + "/home.xhtml"); // Redirect to home page.
} else {
chain.doFilter(req, res); // User is not logged-in, so just continue request.
}
}
// Add/generate init() and destroy() with NOOP.
}
You can also do this in a preRenderView event listener. A #PostConstruct may be too late as the response may already be committed at that point. Note that redirecting without any form of feedback may be confusing for the enduser. In the filter, consider passing an additional parameter which should trigger a conditional message. Or in the preRenderView event listener, set a flash scoped message.

Why is Phase Listener-based JSF redirect not working for AJAX requests when session has timed-out?

I have a JSF Phase Listerner that checks to see if the user is logged in, and if not, redirects them to the login page. This is working fine for non-ajax requests. However, if the user is on a page, in my case, one that has a primefaces data table, and clicks on a button that invokes an ajax request -- but their session has timed out -- the code gets executed that issues the redirect (using ExternalContext#redirect), however the user is not navigated to the login page.
Any idea why this is not working?
Here is my phase listener:
private static final String IS_LOGGED_IN_INDICATOR = "loggedIn";
private static final String LOGIN_PAGE = "/login.jsp";
public PhaseId getPhaseId() {
return PhaseId.RESTORE_VIEW;
}
public void beforePhase(PhaseEvent event) {
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
HttpSession session = (HttpSession)ec.getSession(false);
if (session==null || session.getAttribute(IS_LOGGED_IN_INDICATOR) == null) {
try {
ec.redirect(LOGIN_PAGE);
}
catch(IOException e) {
// log exception...
}
}
}
public void afterPhase(PhaseEvent event) {
// no-op
}
}
It failed because the ajax context is trying to obtain the render kit from the view root, while there is no view root at all. It has not been restored at that point yet. This resulted in a NullPointerException in PartialViewContext#createPartialResponseWriter(). This exception is in turn not been thrown, but instead been put in an ajax exception queue which is supposed to be handled by a custom ExceptionHandler. You apparently don't have any one. This exception is visible if you create/use such one like the FullAjaxExceptionHandler (see also this blog for more detail).
To fix the particular problem, do the job in afterPhase() instead. The view root is then fully restored and the ajax context can obtain the render kit from it in order to write a specialized XML response which instructs the JSF ajax engine in JavaScript to change the window location. Without ajax, a render kit was not necessary as a redirect is basically just a matter of setting a response header.
Whether the particular NullPointerException is in turn a bug in Mojarra or not is a different question which can better be posted in flavor of an issue report at their own issue tracker.
this is because you have to send a special response in XML for Ajax request in order to do redirect (check this answer) , I have implemented this in a Filter like this..
// Check if it's an Ajax Request
if ("partial/ajax".equals(((HttpServletRequest) request).getHeader("Faces-Request"))) {
//redirect
response.setContentType("text/xml");
response.getWriter()
.append("<?xml version= \"1.0\" encoding=\"UTF-8\"?>")
.printf("<partial-response><redirect url=\"%s\"></redirect></partial-response>",url);
you should port this to your Phase Listener.

JSF 2.0 - Checking & redirecting if the user is not logged in

I have implemented a servlet filter that checks whether the user is logged in and redirects the user to the login page if they are not. The reason I am doing this check as a filter is that the login page exists in an another webapp which I cannot seem to redirect to using web.xml's FROM auth-method to redirect to a page that's in a different webapp with a different context root (note, I am using weblogic 11g).
The problem I am experiencing is that when I have an ajaxified component, like a button, the servlet filter is not able to redirect the user. i.e. they wind up right back on the page that they were on.
Is there a different way I should be doing this logged-in check?
I re-implemented the servlet filter as a JSF 2.0 Phase Listener that runs before the RESTORE_VIEW phase. By moving the logic into a Phase Listener, I was able to take advantage of JSF's ability to handle redirects for an AJAX request. Basically JSF 2.0 will create the proper AJAX response to cause a redirect on the client side. To be clear, this mechanism is able to do a redirect for AJAX and non-AJAX requests if the user is not logged in.
Specifically, it will send back the following response:
<?xml version="1.0" encoding="utf-8"?>
<partial-response>
<redirect url="/contextpath/faces/ajax/redirecttarget.xhtml">
</redirect>
</partial-response>"
Code for the phase listener:
public PhaseId getPhaseId()
{
return PhaseId.RESTORE_VIEW;
}
public void afterPhase(PhaseEvent event)
{
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
HttpSession session = (HttpSession)ec.getSession(false);
if (session==null || session.getAttribute(IS_LOGGED_IN_INDICATOR) == null)
{
try
{
ec.redirect(LOGIN_PAGE_URL);
}
catch(IOException e)
{
// log exception...
}
}
}

How to move user to timeout page when session expires, if user click on browser back button

I am handling session expiration in JSF 2.0 using filter . Here is the code
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
HttpSession session = httpServletRequest.getSession(false);
if (session == null) {
//session timeout check.
if (httpServletRequest.getRequestedSessionId() != null && !httpServletRequest.isRequestedSessionIdValid()) {
System.out.println("Session has expired");
session = httpServletRequest.getSession(true);
session.setAttribute("logedin", "0"); // public user
httpServletResponse.sendRedirect(timeoutPage);
} else {
session = httpServletRequest.getSession(true);
session.setAttribute("logedin", "0");
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
} //end of doFilter()
But the problem is, when session expires and if user click on the back button, then he gets the page with all styling out. Is there is anyway that when session expires, and if user click the browser back button, then he directs to the timeoutPage.
One thing more, that i am also using Prime Faces component on my page, like datatable. I am using pagination. If session time out, and i click on pagination then the session expiration message do not appear. It seems that ajax request don't call filter? How can i connect my ajax events, or you can say datatable pagination events to session expiration?
Thanks
when session expires and if user click on the back button, then he gets the page with all styling out
You need to tell the browser to not cache the pages in browser cache. The browser shoud instead be sending a full request to the server.
Add the following lines right before filterChain.doFilter() call.
if (!httpServletRequest.getRequestURI().startsWith(httpServletRequest.getContextPath() + ResourceHandler.RESOURCE_IDENTIFIER)) { // Skip JSF resources (CSS/JS/Images/etc)
httpServletResponse.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1.
httpServletResponse.setHeader("Pragma", "no-cache"); // HTTP 1.0.
httpServletResponse.setDateHeader("Expires", 0); // Proxies.
}
If session time out, and i click on pagination then the session expiration message do not appear. It seems that ajax request don't call filter?
JSF ajax requests expect XML responses with HTTP status 200. If you send a synchronous redirect, then a HTTP status 302 response will be sent which will be completely ignored by JSF ajax engine. You should instead be sending a normal HTTP 200 response with a specific piece of XML which tells the JSF ajax engine to perform a redirect. Do this instead of httpServletResponse.sendRedirect() then:
if ("partial/ajax".equals(httpServletRequest.getHeader("Faces-Request"))) {
httpServletResponse.setContentType("text/xml");
httpServletResponse.getWriter()
.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>")
.printf("<partial-response><redirect url=\"%s\"></redirect></partial-response>", timeoutPage);
}
else {
httpServletResponse.sendRedirect(timeoutPage);
}
Note that when you're already inside JSF context (e.g. by PhaseListener or SystemEventListener or maybe a #ManagedBean), then you could just use ExternalContext#redirect() method. It will transparently handle synchronous/asynchronous requests accordingly.

Resources