jsf 2 -通过客户端状态保存防止jsf中的CSRF



我正在使用MyFaces 2.2.3与客户端状态保存+ PrimeFaces

在询问如何防止在不同的会话中重用ViewState之后,我被BalusC告知,我可以注入我自己的CSRF令牌通过覆盖from渲染器让值成为CSRF令牌

我正在寻找一个解决方案,不会强迫我修改我的xhtml页面:)


BalusC提出了一个更好的方法来防止CSRF攻击扩展ViewHandlerWrapper,它工作得很好,我只需要修改一点restoreView在以下方式

public UIViewRoot restoreView(FacesContext context, String viewId) {
    UIViewRoot view = super.restoreView(context, viewId);
    if (getCsrfToken(context).equals(view.getAttributes().get(CSRF_TOKEN_KEY))) {
        return view;
    } else {
        HttpSession session = (HttpSession) context.getExternalContext().getSession(false);
        if (session != null) {
            session.invalidate(); //invalidate session so (my custom and unrelated) PhaseListener will notice that its a bad session now
        }
        try {
            FacesContext.getCurrentInstance().getExternalContext().redirect("CSRF detected and blocked"); //better looking user feedback
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

<

老解决方案/strong>

<年代>我试过了,但没有成功,

添加到faces-config.xml

<render-kit>
    <renderer>
        <component-family>javax.faces.Form</component-family>
        <renderer-type>javax.faces.Form</renderer-type>
        <renderer-class>com.communitake.mdportal.renderers.CTFormRenderer</renderer-class>
    </renderer>
</render-kit>   

然后在CTFormRenderer.java

@Override
public void encodeEnd(FacesContext context, UIComponent arg1) throws IOException {
    //how to set form value be a CSRF token?
}
@Override
public void decode(FacesContext context, UIComponent component) {
    HttpSession session = (HttpSession) context.getExternalContext().getSession(false);
    String token = (String) session.getAttribute(CSRFTOKEN_NAME);
    String tokenFromForm = //how to get the value stored in form value attribute because (String) component.getAttributes().get("value"); return null
    //check token against tokenFromForm...
}

我不想为每个h:form添加自定义组件,相反,我想扩展form渲染器,因此我的所有表单都将具有csrf token

这种<h:form>渲染器重写方法对PrimeFaces partialSubmit="true"不安全。此外,重用其标识提交表单的隐藏字段将是JSF实现特定的,因为这不是JSF API的一部分。

再考虑一下,直接在JSF视图状态本身中存储CSRF令牌要简单得多。您可以使用如下所示的自定义ViewHandler来实现这一点,它在UIViewRoot中设置了一个属性(将自动保存在JSF视图状态中):

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

请注意,当restoreView()返回null时,JSF将"像往常一样"抛出一个ViewExpiredException

要让它运行,在faces-config.xml中注册如下:

<application>
    <view-handler>com.example.CsrfViewHandler</view-handler>    
</application>

因为它没有服务器端状态保存的附加值,如果需要,您可以在视图处理程序的构造函数中检测当前JSF应用程序是否配置了客户端状态保存:

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

最新更新