I\'m Using MyFaces 2.2.3 with client side state saving + PrimeFaces
After asking how to prevent the re-use of a ViewState in different sessions I was told by BalusC
This
renderer override approach is not safe to PrimeFaces partialSubmit="true"
. Also, reusing its hidden field identifying the submitted form would be JSF implementation specific as this is not part of JSF API.
On a second thought, it's much simpler to store the CSRF token just directly in the JSF view state itself. You can achieve that with a custom ViewHandler
as below which sets an attribute in UIViewRoot
(which get automatically saved in JSF view state):
public class CsrfViewHandler extends ViewHandlerWrapper {
private static final String CSRF_TOKEN_KEY = CsrfViewHandler.class.getName();
private ViewHandler wrapped;
public CsrfViewHandler(ViewHandler wrapped) {
this.wrapped = wrapped;
}
@Override
public UIViewRoot restoreView(FacesContext context, String viewId) {
UIViewRoot view = super.restoreView(context, viewId);
return getCsrfToken(context).equals(view.getAttributes().get(CSRF_TOKEN_KEY)) ? view : null;
}
@Override
public void renderView(FacesContext context, UIViewRoot view) throws IOException, FacesException {
view.getAttributes().put(CSRF_TOKEN_KEY, getCsrfToken(context));
super.renderView(context, view);
}
private String getCsrfToken(FacesContext context) {
String csrfToken = (String) context.getExternalContext().getSessionMap().get(CSRF_TOKEN_KEY);
if (csrfToken == null) {
csrfToken = UUID.randomUUID().toString();
context.getExternalContext().getSessionMap().put(CSRF_TOKEN_KEY, csrfToken);
}
return csrfToken;
}
@Override
public ViewHandler getWrapped() {
return wrapped;
}
}
Do note that when restoreView()
returns null
, JSF will throw a ViewExpiredException
"as usual".
To get it to run, register as below in faces-config.xml
:
com.example.CsrfViewHandler
Because it has no additional value with server side state saving, you can if necessary detect as below in the constructor of the view handler if the current JSF application is configured with client side state saving:
FacesContext context = FacesContext.getCurrentInstance();
if (!context.getApplication().getStateManager().isSavingStateInClient(context)) {
throw new IllegalStateException("This view handler is only applicable when JSF is configured with "
+ StateManager.STATE_SAVING_METHOD_PARAM_NAME + "=" + StateManager.STATE_SAVING_METHOD_CLIENT);
}