通过客户端状态保存防止 JSF2 中的 CSRF
Prevent CSRF in JSF2 with client side state saving
我正在使用带有客户端状态保存的 MyFaces 2.2.3 + PrimeFaces
询问 I was 后,我可以 注入 我自己的 CSRF 令牌 通过 覆盖 from渲染器让值成为 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;
}
}
旧解
到目前为止我试过没有成功,
添加到面部-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.
再三考虑,将 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()
returns null
时,JSF 将抛出 ViewExpiredException
"as usual".
要获得 运行,请在 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);
}
我正在使用带有客户端状态保存的 MyFaces 2.2.3 + PrimeFaces
询问
我正在寻找一个完全不会强迫我修改 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;
}
}
旧解
到目前为止我试过没有成功,
添加到面部-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.
再三考虑,将 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()
returns null
时,JSF 将抛出 ViewExpiredException
"as usual".
要获得 运行,请在 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);
}