次のような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.
}
}
この関数は、CKEditor によって保持されている CMS コンテンツを、JSON を介して Spring への POST AJAX 呼び出しでデータベースに挿入することを目的としています (ボタンがクリックされたとき)。
if(!request){...}
関数本体の先頭にある条件チェックは、(おそらくせっかちなユーザーによる) AJAX 呼び出しの重複を防ぐためのものです。
ランダムに生成されたトークン値は、ページが読み込まれ、JavaScript 変数で取得されるとすぐに非表示フィールドに格納されます。
var csrf_token=$("#token").val();
次のハンドラは、リクエストとともに送信されるヘッダーを設定します。
beforeSend: function (xhr)
{
xhr.setRequestHeader('X-CSRF-Token', csrf_token);
},
なぜヘッダーが必要なのですか? 隠しフィールドとして送信されたトークンだけでは十分ではありませんか? 古い Flash Player のため、ヘッダーを確認する必要がありますか (例として)?
このアプローチでは、サーバー側で、ヘッダーと非表示フィールドの両方の値が一致するかどうかを確認しています (セッション内のそのトークンの存在を確認するとともに、セッション内のトークンの存在を確認することはどこにもありませんただし、言及されています})。
上記の JavaScript 関数によって呼び出される Spring コントローラ クラスのメソッドは次のとおりです。
@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 の可能性があります (同じオリジン ポリシーにより、攻撃者は被害者のブラウザーで Cookie を読み取ったり変更したりできないため)。
その後、なぜヘッダーが必要なのですか? CSRF 攻撃を防ぐ (または少なくとも軽減する) ための推奨される方法は実際には何ですか?
次の回答はそれをうまく要約しています。