JSF Default Page (Resolved: Beware of 301-Caches of Browsers) - jsf-2

We have a JSF-Application running. Ever since the default landing page was defined as /pages/dashboard.xhtml.
Shiro is catching the request, showing the login-page, and after successfull authentication, our servlet retrieves the stored request from shiro and forwards the user to that page.
So, every other kind of deeplink is possible ofc.
Now, we wanted to allow the user to define his default landing page. System is setup, if shiro does not provide a stored request, our application forwards the user to his defined landing-page.
(Other deeplinks still working ofc.)
If the user now calls https://example.com/app/login.xhtml directly, he is forwarded to his custom landing page. (after login)
The only thing that is strange - and drives me crazy by now: If a user only requests https://example.com/app - the first request hiting shiro is the link to the old dashboard again: https://example.com/app/pages/dashboard.xhtml
In the web.xml we mapped the servlet to *.xhtml
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.xhtml</url-pattern>
</servlet-mapping>
and defined the welcome-file-list as
<welcome-file-list>
<welcome-file>index.xhtml</welcome-file>
</welcome-file-list>
where the file exists, and only contains
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Refresh" content="0; URL=login.xhtml">
</head>
</html>
It seems like the file is never invoked. When removing the redirect, calling https://example.com/app immediately leads to https://example.com/app/login.xhtml - but shiro logs an access-request to https://example.com/app/pages/dashboard.xhtml, leading to a "stored request" then.
(Which we don't like, cause that szenario should use the users default landing page)
It's wildfly 8.1, I'm totally out of ideas where this "request" is triggered. (Obviously it's not a default page) - but it's the first request hitting our url-rewrite filter, so it musst happen before the application is invoked...
But where?

Awkward...
<meta http-equiv="Refresh" content="0; URL=login.xhtml">
is considered a 301 (Moved Permanently) and chrome is caching this redirect even if dev-tools (with cache disabled) are open. Only clearing the cache manually solves this.
Thus, chrome never invoked the (new) index.xhtml, cause prior to the change it was <meta http-equiv="Refresh" content="0; URL=/pages/dashboard.xhtml">
So, for the time that cache might be alive on any client the only option seems to be: considering a stored request to the old dashboard as no stored request...
Update: It seems like browser might cache 301s for an infinite amount of time:
How long do browsers cache HTTP 301s?
Means: For now I need to ignore this stored request. If a user sets it up as custom landing page, this will be handled later.
FacesContext context = FacesContext.getCurrentInstance();
ServletRequest request = (ServletRequest) context.getExternalContext().getRequest();
SavedRequest savedRequest = WebUtils.getAndClearSavedRequest(request);
if ((savedRequest != null) && (savedRequest.getRequestUrl() != null)) {
/**
* Browser might cache the 301 redirect from index.xhtml
* for an infinite amount of time. So, as of now, we can only consider
* the old "dashboard" to be an "unstored request".
*
*/
if (!savedRequest.getRequestUrl().contains("/pages/dashboard.xhtml")) {
return savedRequest.getRequestUrl();
}
}
// No stored request. Get Custom Landingpage.
String defaultLandingPage = this.userValueService.getValue(UserValueKey.defaultLandingPage);
return ProjectStageConfiguration.getInstance().getWebContextPath() + defaultLandingPage;

Related

Dynamic view id using pretty faces navigation refuses to work

I am attempting to migrate an application from JSF 1.2 to JSF 2.1. The code below worked in the 1.2.
I am using PrettyFaces 3.3.3, MyFaces 2.1.
in pretty-config.xml:
<url-mapping id="seSite">
<pattern value="/sites/#{seViewChooserBean.urlSiteType}/#{seViewChooserBean.siteId}"/>
<view-id value="#{seViewChooserBean.getSiteViewId}"/>
</url-mapping>
<url-mapping id="seSiteProps">
<pattern value="/sites/#{sePropsBean.urlSiteType}/#{sePropsBean.siteId}/properties"/>
<view-id value="/pages/se/site/props.xhtml"/>
<action>#{sePropsBean.init}</action>
</url-mapping>
I have a request with URL: http://example.com/myapp/sites/object/309847
This request successfully matches the url mapping id "seSite" and getSiteViewId is invoked on seViewChooserBean and returns the result "pretty:seSiteProps". I have debugged and confirmed this. For your reference this is the bean code for ViewChooserBean.java:
public String getSiteViewId() {
if (siteType == SiteType.TYPE) {
// redirect to tag list view
initSiteBean("seTagListBean", TagListBean.class);
return "pretty:seTagList";
}
else {
// redirect to site properties view
initSiteBean("sePropsBean", PropertiesBean.class);
return "pretty:seSiteProps";
}
}
After that prettyfaces then attempts to forward to the new view id seSiteProps but the new generated URL is not processed by pretty faces because (from the logs): "Request is not mapped using PrettyFaces. Continue."
So I get 404 response for URL http:://example.com/myapp/sites/object/309847/properties.
Note that this url match to view id seSiteProps.
I have debugged this up into the pretty faces filter and discovered the following:
After the initial request for http://example.com/myapp/sites/object/309847, the DynaviewEngine.processDynaView is invoked and generates the correct target url http:://example.com/sites/object/309847/properties and forwards via faces request.
Then, with a breakpoint in PrettyFilter.doFilter() I observed the following:
In PrettyFilter.doFilter() method: isUrlMappingForward(req) returns false, therefore request is not processed by prettyfaces. Why??
// isUrlMappingForward returns false. The request has url http:://example.com/myapp/sites/object/309847/properties on it.
if (!isUrlMappingForward(req))
{
mapping = getConfig().getMappingForUrl(url);
}
Also, note that if I put the request http:://example.com/myapp/sites/object/309847/properties directly in the browser the page IS processed by prettyfaces and isUrlMappingForward(req) returns true and it loads correctly in the browser.
I was thinking I've missed something obvious here as the problem hasn't been reported elsewhere as far as I can tell. Any help is greatly appreciated. Thanks.
Brett
Actually I'm very surprised that returning PrettyFaces navigation strings from dynaview methods ever worked. This isn't documented anywhere and I doubt that this has been tested in detail. So basically you are using the dynaview feature in an very weird way.
So I recommend to return plain JSF view IDs instead which should work fine. See the documentation for details:
http://ocpsoft.org/docs/prettyfaces/3.3.3/en-US/html/Configuration.html#config.dynaview

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.

JSF exception handling, get request param

i followed instructions from for implementing exception handling in jsf web app.
my problem is to show attribute value, that i set in ExceptionHandler
here is ExceptionHandler.java
#Override
public void handle() throws FacesException {
final Iterator<ExceptionQueuedEvent> i = getUnhandledExceptionQueuedEvents().iterator();
while (i.hasNext()) {
ExceptionQueuedEvent event = i.next();
ExceptionQueuedEventContext context = (ExceptionQueuedEventContext) event.getSource();
Throwable t = context.getException();
FacesContext fc = FacesContext.getCurrentInstance();
Map<String, Object> requestMap = fc.getExternalContext().getRequestMap();
NavigationHandler nav = fc.getApplication().getNavigationHandler();
try {
// Push some useful stuff to the request scope for
// use in the page
System.err.println("DefaultExceptionHandler.handle()...exceptionMessage = " + t.getMessage());
requestMap.put("exceptionMessage", t.getMessage());
nav.handleNavigation(fc, null, "error/500");
fc.renderResponse();
} finally {
i.remove();
}
}
getWrapped().handle();
}
and 500.xhtml
<ui:composition
xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core">
The error message is:
<br />
<b>
3. : #{exceptionMessage}
</b>
<br />
<b>
1. : #{requestScope['exceptionMessage']}
</b>
<br />
<b>
2. : #{param['exceptionMessage']}
</b>
and page in browser looks like:
The error message is:
3. :
1. :
2. :
thanks in advance!!
First of all, that's not a request parameter at all. Request parameters are the data which the enduser has sent to the server along with the HTTP request. In GET requests they are visible in query string part of the URL. In POST requests, they are hidden in request body (but visible with a HTTP traffic monitor such as browser's builtin one accessible by F12 key). What you were setting is a request attribute. So, from the attempts, only #{exceptionMessage} and #{requestScope['exceptionMessage']} should have worked, but #{param['exceptionMessage']} definitely not.
Coming back to your concrete problem, this can happen when you're sending a redirect on navigation by either <redirect> in navigation case, or by appending ?faces-redirect=true to the outcome somewhere in a custom navigation handler. A redirect basically instructs the client to create a brand new HTTP request, hereby trashing the initial HTTP request including all of its attributes.
If that's also not the problem, then the (customized) FacesContext or ExternalContext implementation being used is likely broken. Hard to tell as you didn't tell anything about the JSF impl/version used nor about the server used nor any "3rd party" libraries. E.g. Spring Web Flow's ExternalContext implementation is known to be broken like that in some versions.

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...
}
}
}

browser back + viewscope beans

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.

Resources