AJAX и CSRF весной
Я отправляю токен CSRF в заголовке HTTP-запроса и в скрытом поле POST для защиты от CSRF-атак, например:
var request;
var timeout;
function insert(callback)
{
if(!request)
{
CKEDITOR.instances.txtAboutProducts.updateElement();
var contents=$("#txtAboutProducts").val();
var csrf_token=$("#token").val();
if(contents==null||contents=='')
{
alert("Please enter the contents.");
return;
}
request = $.ajax({
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
datatype:"json",
type: "POST",
url: "../admin_side/AboutProducts.htm",
data: JSON.stringify({"contents":contents, "token":csrf_token}),
beforeSend: function (xhr)
{
xhr.setRequestHeader('X-CSRF-Token', csrf_token);
},
success: function(response)
{
callback(response);
},
complete: function()
{
timeout = request = null;
},
error: function(request, status, error)
{
if(status!=="timeout"&&status!=="abort") // or just if(status==="error")
{
alert(status+" : "+error);
}
callback(request);
}
});
timeout = setTimeout(function() {
if(request)
{
request.abort();
alert("The request has been timed out.");
}
}, 300000); //5 minutes.
}
}
Эта функция предназначена для вставки содержимого CMS, хранящегося в CKEditor, для вставки в базу данных с помощью вызова POST AJAX для Spring через JSON (при нажатии кнопки).
Условная проверка if(!request){...}
в начале тела функции просто для предотвращения дублирования вызовов AJAX (вероятно, нетерпеливыми пользователями).
Произвольно сгенерированное значение токена сохраняется в скрытом поле, как только страница загружается и извлекается в переменной JavaScript.
var csrf_token=$("#token").val();
Затем следующий обработчик устанавливает заголовок, который должен быть отправлен вместе с запросом.
beforeSend: function (xhr)
{
xhr.setRequestHeader('X-CSRF-Token', csrf_token);
},
Зачем нужен заголовок? Токен, отправленный как скрытое поле, сам по себе недостаточен? Нужно ли проверять заголовок из-за старых Flash Player (просто в качестве примера)?
При таком подходе на стороне сервера я проверяю, совпадают ли значения как заголовка, так и скрытого поля (вместе с проверкой наличия этого токена в сеансе {проверка наличия токена в сеансе нигде хотя упоминалось}).
Метод в классе контроллера Spring, который вызывается вышеупомянутой функцией JavaScript, выглядит следующим образом.
@RequestMapping(value=("admin_side/AboutProducts"), method=RequestMethod.POST)
private @ResponseBody CKEditorContentsHandler insert(@RequestBody final CKEditorContentsHandler object, final HttpServletResponse response, final HttpServletRequest request)
{
if(object!=null&&StringUtils.isNotBlank(object.getToken())&&StringUtils.isNotBlank(request.getHeader("X-CSRF-Token"))&&sessionTokenService.isTokenValid(object.getToken())&&object.getToken().equals(request.getHeader("X-CSRF-Token")))
{
aboutProductsService.insert(object.getContents());
object.setMessage("Insertion done successfully.");
object.setStatus(1);
}
else
{
object.setMessage("The authentication token cannot be verified.");
object.setStatus(-1);
}
return object;
}
куда CKEditorContentsHandler
первый параметр этого метода - простой Java-класс, содержащий всего несколько свойств для удовлетворения потребностей.
Также рекомендуется сохранить токен в файле cookie и отправить его вместе с опубликованными данными (скрытое поле) и проверить, совпадают ли опубликованные данные и значения файлов cookie. Если они этого не делают, то это возможный хак CSRF (потому что злоумышленники не могут читать или изменять куки в браузере жертвы из-за той же политики происхождения).
После чего требуется заголовок? Каков на самом деле рекомендуемый способ защиты от (или, по крайней мере, смягчения) атак CSRF?
Следующий ответ суммирует это хорошо.