Prevent CSRF in JSF2 with client side state saving

后端 未结 1 1888
遇见更好的自我
遇见更好的自我 2021-01-06 10:53

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

1条回答
  •  一整个雨季
    2021-01-06 11:32

    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);
    }
    

    0 讨论(0)
提交回复
热议问题