The Java EE 6 Example – Galleria – Part 3
Session Fixation
The hottest topic for Enterprise Java applications out there is security. And because it has so many different aspects I decided to start with a very simply but often required feature: Session Fixation prevention. This isn’t very Java or JSF specific but a general problem for web based applications. Session fixation arises when session IDs are easy to discover or guess. The main method of attack is when the session ID is present in the URL or any other part of the response. An attacker could capture a session and then embed the link in their page, tricking a user into visiting it and becoming part of their session. Then when the user authenticates the session is authenticated. Using Cookies only gives a certain security here because the are most often also set via a method which implies confidentiality lose. Most application servers generate a new session ID with the first request. After this is authenticated it is re-used further on. The only way to prevent this is to issue a new, random session after a successful authentication request.
This is easy to do in general. Go to the galleria-jsf project and find the info.galleria.view.user.Authenticator bean. Add the following lines to the beginning of the authenticate() method:
String result = null; ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext(); // Session Fixation Prevention HttpSession session = (HttpSession) externalContext.getSession(false); if (logger.isDebugEnabled()) { logger.debug("Session before authentication request: " + session.getId()); } session.invalidate(); session = (HttpSession) externalContext.getSession(true); if (logger.isDebugEnabled()) { logger.debug("Session after authentication request: " + session.getId()); }
That was it. Very easy change for the first time touching the code-base. Switching to debug level FINE for package info.galleria should reveal the magic in the logfile:
[#|2012-03-27T17:17:25.298+0200|FINE|glassfish3.1.2|info.galleria.view.user.Authenticator|_ThreadID=27; _ThreadName=Thread-4;ClassName=info.galleria.view.user.Authenticator;MethodName= authenticate;|Session before authentication request: 33b1205d7ad740631978ed211bce|#] [#|2012-03-27T17:17:25.301+0200|FINE|glassfish3.1.2|info.galleria.view.user.Authenticator |_ThreadID=27 ;_ThreadName=Thread-4;ClassName=info.galleria.view.user.Authenticator;MethodName =authenticate;|Session after authentication request: 33b1f344ad1730c69bccc35e752e|#]
As expected we changed the http session during the authentication request. You can also check this with a browser add-on of your choice (in this case “Edit This Cookie”):
And the Galleria application got a bit securer by doing this. If you want to learn more about Session Fixation give the OWASP page a read.
Prevent Multiple Logins
The next requirement is a bit more complex. I have seen this a couple of times and even if it is inconvenient for the user it could be necessary for security reasons. As you might have guessed, there isn’t a single switch for that. You have to hold a map of sessions and check if a user is already logged in or not. It should be checked during the login-process and a meaningful error message should be displayed.
There are some tricky parts in that. First one is, that you need a way to store all your user and HttpSession information for the application. And second one is, that you need a someone to look after it. Let’s start with the latest.
You are in need of the famous Singleton here. A single place to store the relevant HttpSession information. First thought would be to use the .getExternalContext().getApplicationMap(). This could work. The login restriction we are placing here has some side-effects. Imagine a user loged-in and crashing his/her browser without doing a logout before. He/she would end up with not being able to login again until some cleanup or application restart happens. So it is crucial to also have access to it in a HttpSessionListener. Given the fact, that the JSF ExternalContext is the ServletContext we are safe here.
Before proceeding one more word about clustering. We are going to build a non-clusterable construct here. According to the servlet specification, context attributes are local to the JVM in which they were created. So you will lose protection if you run this in a clustered environment because you can have a session on every single node of the cluster. Making this cluster safe would mean to use either the database, an ejb component or a distributed cache.
Go to info.galleria.view.util and create a new final class with the name SessionConcierge. It needs methods for adding and removing a session. And we obviously need something to handle the application map. Starting with the addSession method which will be called from the info.galleria.view.user.Authenticator managed bean later on:
public static boolean addSession(HttpSession session) { String account = FacesContext.getCurrentInstance().getExternalContext().getRemoteUser(); String sessionId = session.getId(); if (account != null && !getApplicationMap(session).containsKey(account)) { getApplicationMap(session).put(account, sessionId); if (logger.isDebugEnabled()) { logger.debug("Added Session with ID {} for user {}", sessionId, account); } return true; } else { logger.error("Cannot add sessionId, because current logged in account is NULL or session already assigned!"); return false; } }
This basically checks if we have a loged-in user here and if the user already has a session assigned. If there is a user and he does not have a session in use already we are going to add the current session to the application map under the account as a key. Next a bit remove logic:
public static void removeSession(HttpSession session) { String sessionId = session.getId(); String account = getKeyByValue(getApplicationMap(session), sessionId); if (account != null) { getApplicationMap(session).remove(account); if (logger.isDebugEnabled()) { logger.debug("Removed Session with ID {} for user {}", sessionId, account); } } }
This is a bit more tricky. You noticed, that I use the account as a key for binding the session in the map. So I have to trick around a bit to invert the map and find a key by a value. This little magic happens here:
private static <T, E> T getKeyByValue(Map<T, E> map, E value) { for (Entry<T, E> entry : map.entrySet()) { if (value.equals(entry.getValue())) { return entry.getKey(); } } return null; }
Done. One thing is missing. The getApplicationMap(HttpSession session) method. This isn’t very magic. It simply tries to figure out if we need to get it via the FacesContext or the ServletContext. Look at the SessionConcierge source if you are curious. Final thing to do is to add the SessionConcierge to the Authenticator. Add this code into the try{request.login()} (I added the first two lines for your orientation:
request.login(userId, new String(password)); result = "/private/HomePage.xhtml?faces-redirect=true"; // save sessionId to disable multiple sessions per user if (!SessionConcierge.addSession(session)) { request.logout(); logger.error("User {} allready logged in with another session", userId); FacesMessage facesMessage = new FacesMessage(FacesMessage.SEVERITY_ERROR, Messages.getString( "Login.AllreadyLoggedIn", locale), null); FacesContext.getCurrentInstance().addMessage(null, facesMessage); }
If the addition of the HttpSession via the SessionConcierge isn’t successful, the user is loged-out immediately and a FacesMessage is added. Remember to add this to the galleria-jsf\src\main\resources\resources messages.properties and it’s translations. And don’t forget to add the
SessionConcierge.removeSession(session);
to public String logout(). Fine. That’s all, isn’t it? At least it is working for now. But we still have to address those crashing browser issue. If someone isn’t doing a logout via the application, the session times out or the browser crashes you will not be able to login again until the application is restarted. That is weird and unintended. Some mechanism for cleaning up is needed. What about a HttpSessionListener? That sounds great! Add it to info.galleria.listeners and call it SessionExpirationListener.
@Override public void sessionDestroyed(HttpSessionEvent se) { HttpSession session = se.getSession(); SessionConcierge.removeSession(session); if (logger.isDebugEnabled()) { logger.debug("Session with ID {} destroyed", session.getId()); } }
Fine. That should be working now. Go ahead and give it a try. Open two different browsers and try to login with both. Only one will let you access the application. The second should respond with the error message you put into the messages.properties. Please note, that this isn’t a multiple window prevention. You are still free to open as many windows per HttpSession as you like.
One small addition: If you rely heavily on the HttpSessionListener cleanup you should make sure to have a right lifetime for it. It is configured via the product specific web-app deployment descriptor (e.g. weblogic.xml or glassfish-web.xml). I recommend to set it to a reasonable low value (e.g. 30 minutes or less) to not let the users wait for too long. Here is how this would look like for Glassfish (glassfish-web.xml):
<session-config> <session-properties> <property name="timeoutSeconds" value="1800" /> </session-properties> </session-config>
and for WebLogic (weblogic.xml)
<session-descriptor> <timeout-secs>180</timeout-secs> </session-descriptor>
The Galleria Java EE 6 example app is growing . Today I am going to write about how to gracefully deal with errors. A lot has been done about user input validation already but there are still a lot of failure situations which are not handled but should be. If you are curious about what happened in the past look at the first parts of the series: The basics , running it on GlassFish, running it on WebLogic, testing it and enhanced security.
General Exception Mechanism
The application uses checked exceptions to communicate errors between the layers. The ApplicationException is the root of all possible business exceptions.
Those business exceptions communicate validation violations and all known errors between the domain and the presentation layer. The <domain>Manager (e.g. AlbumManger) classes in the galleria-jsf view project catch them and use the ExceptionPrecessor to populate the error messages to the view. The other kind of Exceptions that could occur between those two layers are RuntimeExceptions. Those get wrapped into an EJBException by the container and are also caught by the The <domain>Manager classes. Those generate a more general error message which is shown to the user.
I am not going to jump in on checked vs. unchecked exceptions here (Google a bit about it if you are curious). I tend to use checked exceptions when the application has a chance to recover from the error. Unchecked are thrown when something happens which isn’t recoverable. That is the reason, I am not happy with the exception handling mechanism build in at the moment. I am going to get into this a little later.
What is missing? ViewExpired and more.
Seems as if everything is handled right now. But only on the first impression. Open the login screen and wait a bit and let your http session timeout. You are now greeted with a not so nice ViewExpired exception screen.
If you are trying this as a loged-in user you are simply redirected to the login page. Anyway, the same error page could come up for some other unexpected conditions in the presentation layer. So, let’s fix this. Most obvious thing to do is to simply introduce a dedicated error-page.
<error-page> <exception-type>javax.faces.application.ViewExpiredException</exception-type> <location>/viewExpired.xhtml</location> </error-page>
Now you redirect your users to a dedicated page which could tell him/her something nice about workplace security and not leaving the app unattended for such a long time. This works for most of the applications out there. If you are willing to have some additional information on the page or simply want to catch more than one exception and handle them individually without having to configure them statically, you need something called an ExceptionHandler. This is new in JSF 2 and all you need to do is to implement an ExceptionHandler and it’s factory. The factory itself is configured in the facex-config.xml because there isn’t any annotation for it.
Open the faces-config.xml and add the following lines at the bottom:
<factory> <exception-handler-factory>info.galleria.handlers.GalleriaExceptionHandlerFactory</exception-handler-factory> </factory>
Now we are going to implement the GalleriaExceptionHandlerFactory in the dedicated package. The interesting method here is the:
@Override public ExceptionHandler getExceptionHandler() { ExceptionHandler result = parent.getExceptionHandler(); result = new GalleriaExceptionHandler(result); return result; }
This is called once per request must return a new ExceptionHandler instance each time it’s called. Here the the real ExceptionHandlerFactory is called and asked to create the instance, which is then wrapped in the custom GalleriaExceptionHandler class. This is where the real interesting stuff happens.
@Override public void handle() throws FacesException { for (Iterator<ExceptionQueuedEvent> i = getUnhandledExceptionQueuedEvents().iterator(); i.hasNext();) { ExceptionQueuedEvent event = i.next(); ExceptionQueuedEventContext context = (ExceptionQueuedEventContext) event.getSource(); Throwable t = context.getException(); if (t instanceof ViewExpiredException) { ViewExpiredException vee = (ViewExpiredException) t; FacesContext fc = FacesContext.getCurrentInstance(); Map<String, Object> requestMap = fc.getExternalContext().getRequestMap(); NavigationHandler nav = fc.getApplication().getNavigationHandler(); try { // Push some stuff to the request scope for later use in the page requestMap.put("currentViewId", vee.getViewId()); nav.handleNavigation(fc, null, "viewExpired"); fc.renderResponse(); } finally { i.remove(); } } } // Let the parent handle all the remaining queued exception events. getWrapped().handle(); }
Iterate over the unhandler exceptions using the iterator returned from getUnhandledExceptionQueuedEvents().iterator(). The ExeceptionQueuedEvent is a SystemEvent from which you can get the actual ViewExpiredException. Finally you extract some extra information from the exception and place it in request scope to access it via EL in the page later on. Last thing to do here for a ViewExpiredException is to use the JSF implicit navigation system (“viewExpired” is resolved to “viewExpired.xhtml”) and navigate to the “viewExpired” page via the NavigationHandler. Don’t forget to remove the handled exception in the finally block. You don’t want this to be handled again by the parent exception handler. Now we have to create the viewExpired.xhtml page. Do this inside the galleria-jsf\src\main\webapp folder.
<?xml version='1.0' encoding='UTF-8' ?> <!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <ui:composition xmlns:ui="http://java.sun.com/jsf/facelets" template="./templates/defaultLayout.xhtml" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" > <ui:define name="title"> <h:outputText value="#{msg['Exception.page.title']}" /> </ui:define> <ui:define name="content"> <h:form> <h:outputText value="#{msg['Exception.page.message']}" /> <p>You were on page #{currentViewId}. Maybe that's useful.</p> <p>Please re-login via the <h:outputLink styleClass="homepagelink" value="#{request.contextPath}/Index.xhtml" ><h:outputText value="Homepage" /></h:outputLink>.</p> </h:form> </ui:define> </ui:composition>
Please note that I added new message properties here, so you need to make sure to place them in galleria-jsf\src\main\resources\resources\messages.properties and translations.
Until now this obviously only handles one special instance of exception. You could extend it to handle others as well. Now that we have the basic mechanism in place you are free to do this.
Refactoring the RuntimeException handling
As I said, I am not happy with the way the application is handling RuntimeExceptions. Now that we have a nice central exception handling in place we can move those stuff around a bit and refactor the *Manager classes. Delete all those catch (EJBException ejbEx) { blocks from all of them. We are going to take care of them in the GalleriaExceptionHandler in a minute. Simply add an another check to the GalleriaExceptionHandler and redirect the user to another page if any other exception than a ViewExpiredException is thrown.
// check for known Exceptions if (t instanceof ViewExpiredException) { ViewExpiredException vee = (ViewExpiredException) t; // Push some stuff to the request scope for later use in the page requestMap.put("currentViewId", vee.getViewId()); } else { forwardView = "generalError"; Locale locale = fc.getViewRoot().getLocale(); String key = "Excepetion.GeneralError"; logger.error(Messages.getLoggerString(key), t); String message = Messages.getString(key, locale); FacesMessage facesMessage = new FacesMessage(FacesMessage.SEVERITY_ERROR, message, null); fc.addMessage(null, facesMessage); }
This approach has some advantages. It reduces the needed code in the *Manager classes and we finally have a central place to take care of those unrecoverable exceptions. This still is not very enterprise like. Imagine your first level support team needs to look after customers and they start complaining that the only message they get is a “GeneralError”. That is not very helpful. You support team would need to escalate it and second or third level would need to check the logs and and and .. All this because of an error, that we could have know. First thing to do is to find out about the causing error. Parsing stack traces isn’t big fun. Especially not of RuntimeExceptions that are wrapped in EJBExceptions and further on in FacesExceptions. Thank god for the Apache Commons ExceptionUtils. Open your galleria-jsf pom.xml and add them as dependency:
<dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>2.6</version> </dependency>
Now you can start to examine the root cause:
} else { forwardView = "generalError"; // no known instance try to specify Throwable causingEx = ExceptionUtils.getRootCause(t); if (causingEx == null) { causingEx = t; }
//... logger.error(Messages.getLoggerString(key), t); requestMap.put("errorCode", errorCode);
Don’t forget to also log the complete stack-trace (t and not only causingEx) here. In general it’s a bad thing to let users know about exceptions. Nobody really wants to see errors happen (because we do hate making mistakes) and after all exception stack-traces could disclose sensitive information which you wouldn’t like to see on a screen somewhere. So you need to find a way to display something meaningful to the user without disclosing too much. That is where the famous error-codes come into play. Use the root-cause exception as message key or make your own decisions on what effort you are going to put in here. It might be a system of categories of errors (db, interface systems, etc.) which give the first-level support a good hint about what was causing the error. I would stick to a simpler solution from the beginning. Simply generate a UUID for every caught exception and trace it to both the log and the UI. A very simple one could be the following.
String errorCode = String.valueOf(Math.abs(new Date().hashCode()));
This should also be added to the message properties and don’t forget that you need another one for the generalError template. If slf4j would use the same message format than jdk logging does you would only need one property .. anyway:
Exception.generalError.log=General Error logged: {}.
Exception.generalError.message=A general error with id {0} occured. Please call our hotline.
Add this to the generalError.xhtml and see how the error code is passed to the message template.
<h:outputFormat value="#{msg['Exception.generalError.message']}" > <f:param value="#{errorCode}"/> </h:outputFormat>
There is still a lot to improve on here. You could use the javax.faces.application.ProjectStage to lookup the current mode the application is running in. If you are running in ProjectStage.Development you could also put the complete stack-trace to the UI and make debugging live a bit easier. The following snippet is trying to get the ProjectStage from JNDI.
public static boolean isProduction() { ProjectStage stage = ProjectStage.Development; String stageValue = null; try { InitialContext ctx = new InitialContext(); stageValue = (String) ctx.lookup(ProjectStage.PROJECT_STAGE_JNDI_NAME); stage = ProjectStage.valueOf(stageValue); } catch (NamingException | IllegalArgumentException | NullPointerException e) { logger.error("Could not lookup JNDI object with name 'javax.faces.PROJECT_STAGE'. Using default 'production'"); } return ProjectStage.Production == stage; }
What about the 3-digit Http Error Pages?
That is another thing to take care of. All those remaining 3-digit http error codes which return one of those not nice looking error pages. The only thing to do this is to map them in the web.xml like shown in the following:
<error-page> <error-code>404</error-code> <location>/404.xhtml</location> </error-page>
You should make sure to have those mappings in place and present a meaningful error to your users. It should become best practice to always offer a way to navigate further from there.
Reference: The Java EE 6 Example – Enhance Security with Galleria – Part 5 ,The Java EE 6 Example – Gracefully dealing with Errors in Galleria – Part 6 from our JCG partner Markus Eisele at the Enterprise Software Development with Java blog.