通过保存客户端状态来防止JSF2中的CSRF [英] Prevent CSRF in JSF2 with client side state saving

查看:184
本文介绍了通过保存客户端状态来防止JSF2中的CSRF的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm Using MyFaces 2.2.3 with client side state saving + PrimeFaces

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

After asking how to prevent the re-use of a ViewState in different sessions I was told by BalusC , that I can inject my own CSRF token by override the from renderer to let the value be a CSRF token ,

我正在寻找一种解决方案,该解决方案根本不会迫使我修改我的 xhtml 页面:)

I'm looking for a solution that wont force me to modify my xhtml pages at all :)

BalusC通过扩展ViewHandlerWrapper提出了一种更好的方法来防止CSRF攻击,并且效果很好,我只需要按以下方式修改restoreView

BalusC has suggested a better way to prevent CSRF attack by extending ViewHandlerWrapper , and it works great, I only had to modify a bit the restoreView in the following way

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


旧解决方案

到目前为止,我尝试都没有成功,

I tried without success so far,

已添加到faces-config.xml

Added to 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.

I don't want to add a custom component to each and every h:form, instead I want to extend the form renderer so all my form will have the csrf token.

推荐答案

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

This <h:form> 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.

再三考虑,将CSRF令牌直接存储在JSF视图状态本身中要容易得多.您可以使用以下自定义ViewHandler实现此目标,该自定义ViewHandlerUIViewRoot中设置属性(该属性会自动保存为JSF视图状态):

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

}

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

Do note that when restoreView() returns null, JSF will throw a ViewExpiredException "as usual".

要使其运行,请在faces-config.xml中进行以下注册:

To get it to run, register as below in faces-config.xml:

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

因为它在保存服务器端状态方面没有任何附加价值,所以如果当前JSF应用程序配置了客户端状态保存,则可以在必要时在视图处理程序的构造函数中进行以下检测:

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

这篇关于通过保存客户端状态来防止JSF2中的CSRF的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆