通过客户端状态保存防止 JSF2 中的 CSRF
Posted
技术标签:
【中文标题】通过客户端状态保存防止 JSF2 中的 CSRF【英文标题】:Prevent CSRF in JSF2 with client side state saving 【发布时间】:2015-08-11 00:14:52 【问题描述】:我正在使用 MyFaces 2.2.3 和客户端状态保存 + PrimeFaces
在询问how to prevent the re-use of a ViewState in different sessions 我是told by BalusC 之后,我可以注入我自己的CSRF 令牌,方法是覆盖来自渲染器,让值成为CSRF 令牌 ,
我正在寻找一种完全不会强迫我修改我的 xhtml 页面的解决方案 :)
BalusC 提出了一种更好的方法来通过扩展 ViewHandlerWrapper
来防止 CSRF 攻击,并且效果很好,我只需要通过以下方式对 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;
旧解决方案
到目前为止我尝试过没有成功,
添加到 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
。
【问题讨论】:
它在没有 PrimeFaces 的情况下是否可以使用纯 jsf(只是为了确定)?如果没有,它是否适用于 Mojarra? 我对您的方向并不完全清楚:“我可以通过覆盖 from 渲染器来注入我自己的 CSRF 令牌,让值成为 CSRF 令牌”这是否意味着您要指定 CSRF手动令牌?你知道它必须与服务器最初生成的内容相对应吗? @kolossus ,您可以在我之前的问题和 BalusC 的回答 ***.com/questions/30373089/… 中详细了解我为什么需要这个解决方案 您是否也打算生成此令牌?或者您想检索股票 JSF 值? 我打算自己生成 【参考方案1】:这种<h:form>
渲染器覆盖方法对 PrimeFaces partialSubmit="true"
不安全。此外,重用其标识提交表单的隐藏字段将是特定于 JSF 实现的,因为这不是 JSF API 的一部分。
再想一想,将 CSRF 令牌直接存储在 JSF 视图状态本身中要简单得多。您可以使用自定义 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);
【讨论】:
谢谢!看起来正是我需要的,明天将测试它:) b.t.w 是否可以重定向到一些自定义错误页面以防不等于而不是返回 null 并显示 ViewExpiredException 页面? 检查了它,尽管getCsrfToken(context).equals(view.getAttributes().get(CSRF_TOKEN_KEY))
是 false 并且在 restoreView 中返回了 null ,jsf 仍然继续使用 @987654334 的 action
方法 @ 和外部 html 仍然设法发布其数据,但如果我将 return null;
替换为 FacesContext.getCurrentInstance().getExternalContext().redirect("somePage.jsf"); return null
它将不会继续执行操作方法
嗯,应用中还有更多的视图处理程序吗?
不,它是唯一的一个,实际上重定向为我提供flow hijack
它并不是它实际上重定向,因为外部表单提交返回一个xml并且浏览器显示:This XML file does not appear to have any style information associated with it. The document tree is shown below. <partial-response> <redirect url="somePage.jsf"/> </partial-response>
跨度>
无法重现 JSF 仍继续使用 MyFaces 2.2.8 的操作方法。您使用哪些上下文参数设置?以上是关于通过客户端状态保存防止 JSF2 中的 CSRF的主要内容,如果未能解决你的问题,请参考以下文章
在基于 OAuth2 的身份验证中,状态参数可以防止啥样的 CSRF 攻击?