We use something like
/**
* A {@link Form} that adds cross-site request forgery protection.
*
* @param <T>
* The model object type
*/
public class CSRFForm<T> extends Form<T> {
// serves as a mean to transfer CSRF protection token between
server and client (and vice versa)
private String clientCSRFToken;
private final String serverCSRFToken;
private HiddenField<String> csrfTokenHiddenField;
// we can't use LogCategory as it is not visible here.
private static final Logger LOGGER = LoggerFactory.getLogger("Security");
public CSRFForm(String id) {
super(id);
clientCSRFToken = generateCSRFToken();
// keep an immutable copy on the server side.
serverCSRFToken = clientCSRFToken;
}
public CSRFForm(String id, IModel<T> model) {
super(id, model);
clientCSRFToken = generateCSRFToken();
// keep an immutable copy on the server side.
serverCSRFToken = clientCSRFToken;
}
/**
* Override to use a different way to generate a token.
*
* @return a CSRF token
*/
protected String generateCSRFToken() {
return UUID.randomUUID().toString();
}
@Override
protected void onInitialize() {
super.onInitialize();
// client will submit clientCSRFToken, and it will be set in
clientCSRFToken on the server side
// initially server sets this value, and it is expecting it
back, unchanged, from the client
csrfTokenHiddenField = new HiddenField<>("CRSFFormVersion",
LambdaModel.of(CSRFForm.this::getClientCSRFToken,
CSRFForm.this::setClientCSRFToken), String.class);
add(csrfTokenHiddenField);
}
@Override
protected void delegateSubmit(IFormSubmitter submittingComponent) {
// we compare the version stored on the server with the version
the client sent
// form is only processed
if (serverCSRFToken.equals(clientCSRFToken)) {
super.delegateSubmit(submittingComponent);
} else {
String message = String.format("Preventing possible CSRF
attack! Client clientCSRFToken=%s and server serverCSRFToken=%s do not
match!", clientCSRFToken, serverCSRFToken);
LOGGER.error(message);
throw new WicketRuntimeException(message);
}
}
@Override
public void onComponentTagBody(MarkupStream markupStream,
ComponentTag openTag) {
getResponse().write("<input type=\"hidden\" class=\"csrf-form\"
name=\"" + csrfTokenHiddenField.getInputName() + "\" value =\"" +
Strings.escapeMarkup(serverCSRFToken) + "\"/>");
super.onComponentTagBody(markupStream, openTag);
}
public String getClientCSRFToken() {
return clientCSRFToken;
}
public void setClientCSRFToken(String clientCSRFToken) {
this.clientCSRFTokaen = clientCSRFToken;
}
}
we also use a in order to spot non-secure forms
public class CSRFFromChecker implements IComponentInitializationListener {
private static final CSRFFromChecker INSTANCE = new CSRFFromChecker();
// we can't use LogCategory as it is not visible here.
private static final Logger LOGGER = LoggerFactory.getLogger("Security");
public static CSRFFromChecker getInstance() {
return INSTANCE;
}
@Override
public void onInitialize(Component component) {
if (component instanceof Form<?> && !(component instanceof
CSRFForm<?>)) {
LOGGER.warn("Form {} is not a form or CSRF form",
component.getPath());
}
}
}
Ernesto Reinaldo Barreiro
Apache Wicket Committer