WebページからAJAXスタイルと呼ばれる既存のMVCWebサービスがいくつかあります。これらのサービスは、ValidateAntiForgeryToken属性を利用して、リクエストフォージェリを防止します。
これらのサービスをWebAPIに移行することを検討していますが、同等の偽造防止機能はないようです。
私は何かが足りないのですか?Web APIを使用してリクエストフォージェリに対処するための別のアプローチはありますか?
WebページからAJAXスタイルと呼ばれる既存のMVCWebサービスがいくつかあります。これらのサービスは、ValidateAntiForgeryToken属性を利用して、リクエストフォージェリを防止します。
これらのサービスをWebAPIに移行することを検討していますが、同等の偽造防止機能はないようです。
私は何かが足りないのですか?Web APIを使用してリクエストフォージェリに対処するための別のアプローチはありますか?
このような認証属性を実装できます:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public sealed class ValidateAntiForgeryTokenAttribute : FilterAttribute, IAuthorizationFilter
{
public Task<HttpResponseMessage> ExecuteAuthorizationFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
{
try
{
AntiForgery.Validate();
}
catch
{
actionContext.Response = new HttpResponseMessage
{
StatusCode = HttpStatusCode.Forbidden,
RequestMessage = actionContext.ControllerContext.Request
};
return FromResult(actionContext.Response);
}
return continuation();
}
private Task<HttpResponseMessage> FromResult(HttpResponseMessage result)
{
var source = new TaskCompletionSource<HttpResponseMessage>();
source.SetResult(result);
return source.Task;
}
}
そしてそれであなたのAPIアクションを飾ります:
[ValidateAntiForgeryToken]
public HttpResponseMessage Post()
{
// some work
return Request.CreateResponse(HttpStatusCode.Accepted);
}
このリンクは役に立ちました。カミソリ ビューから偽造防止トークンを取得し、トークンをヘッダーとして渡すことができます。
var csrfToken = $("input[name='__RequestVerificationToken']").val();
$.ajax({
headers: { __RequestVerificationToken: csrfToken },
type: "POST",
dataType: "json",
contentType: 'application/json; charset=utf-8',
url: "/api/products",
data: JSON.stringify({ name: "Milk", price: 2.33 }),
statusCode: {
200: function () {
alert("Success!");
}
}
});
これについてもう少し考えた後、偽造防止トークンの目的全体を無効にするため、Cookie とフォーム トークンを混在させるのは悪い考えです。フォーム部分を認証ヘッダーに移動している間、Cookie 部分を Cookie として保持することをお勧めします。したがって、この新しい回答 (再び AuthorizeAttribute として) です。
using System;
using System.Linq;
using System.Net.Http;
using System.Web;
using System.Web.Helpers;
using System.Web.Http;
using System.Web.Http.Controllers;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class ApiValidateAntiForgeryToken : AuthorizeAttribute {
public const string HeaderName = "X-RequestVerificationToken";
private static string CookieName => AntiForgeryConfig.CookieName;
public static string GenerateAntiForgeryTokenForHeader(HttpContext httpContext) {
if (httpContext == null) {
throw new ArgumentNullException(nameof(httpContext));
}
// check that if the cookie is set to require ssl then we must be using it
if (AntiForgeryConfig.RequireSsl && !httpContext.Request.IsSecureConnection) {
throw new InvalidOperationException("Cannot generate an Anti Forgery Token for a non secure context");
}
// try to find the old cookie token
string oldCookieToken = null;
try {
var token = httpContext.Request.Cookies[CookieName];
if (!string.IsNullOrEmpty(token?.Value)) {
oldCookieToken = token.Value;
}
}
catch {
// do nothing
}
string cookieToken, formToken;
AntiForgery.GetTokens(oldCookieToken, out cookieToken, out formToken);
// set the cookie on the response if we got a new one
if (cookieToken != null) {
var cookie = new HttpCookie(CookieName, cookieToken) {
HttpOnly = true,
};
// note: don't set it directly since the default value is automatically populated from the <httpCookies> config element
if (AntiForgeryConfig.RequireSsl) {
cookie.Secure = AntiForgeryConfig.RequireSsl;
}
httpContext.Response.Cookies.Set(cookie);
}
return formToken;
}
protected override bool IsAuthorized(HttpActionContext actionContext) {
if (HttpContext.Current == null) {
// we need a context to be able to use AntiForgery
return false;
}
var headers = actionContext.Request.Headers;
var cookies = headers.GetCookies();
// check that if the cookie is set to require ssl then we must honor it
if (AntiForgeryConfig.RequireSsl && !HttpContext.Current.Request.IsSecureConnection) {
return false;
}
try {
string cookieToken = cookies.Select(c => c[CookieName]).FirstOrDefault()?.Value?.Trim(); // this throws if the cookie does not exist
string formToken = headers.GetValues(HeaderName).FirstOrDefault()?.Trim();
if (string.IsNullOrEmpty(cookieToken) || string.IsNullOrEmpty(formToken)) {
return false;
}
AntiForgery.Validate(cookieToken, formToken);
return base.IsAuthorized(actionContext);
}
catch {
return false;
}
}
}
次に、コントローラーまたはメソッドを [ApiValidateAntiForgeryToken] で装飾します。
そして、これを剃刀ファイルに追加して、JavaScript のトークンを生成します。
<script>
var antiForgeryToken = '@ApiValidateAntiForgeryToken.GenerateAntiForgeryTokenForHeader(HttpContext.Current)';
// your code here that uses such token, basically setting it as a 'X-RequestVerificationToken' header for any AJAX calls
</script>