ASP.NET MVC 의 WEB API 에서 CSRF (Cross Site Request Forgery) 공격에 대한 대응 방안을 알아보고자 합니다.
먼저 FORM 태그 내부에 TOKEN 을 생성해야 합니다. Html.AntiForgeryToken() 함수를 통해 얻을 수 있습니다.
<form method="post" id="contact" novalidate name="contactForm">
@Html.AntiForgeryToken()
JQUERY 를 통해 ASP.NET MVC 의 WEB API 를 호출하고자 할 경우, 바인딩 된 TOKEN 을 headers 에 할당하여 전송합니다.
function AutoSignIn() {
var url = "/api/Login";
var csrfToken = $("input[name='__RequestVerificationToken']").val();
jQuery.ajax({
headers: { __RequestVerificationToken: csrfToken },
dataType: "json",
url: url,
async: false,
type: 'POST',
contentType: 'application/json; charset=utf-8'
}).success(function (data) {
location = "/Home/Index";
}).fail(function (jqXHR, textStatus) {
alert(jqXHR.statusText);
});
}
이제 web api 를 호출하기 앞서서, filter 를 만들어 validation 을 체크 하는게 controller 역할과 filter 역할을 나눠서 코딩하면 깔끔해 집니다.
아래는 filter 를 custom 화 하여 구현한 부분입니다. token 키값을 통해 값을 받아 존재 여부를 확인하고 AntiForgery.Validate() 함수에 값을 할당하면 됩니다. 없으면 에러 반환을 하도록 아래와 같이 구현합니다.
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public sealed class ValidateAjaxAntiForgeryToken : ActionFilterAttribute
{
private const string HeaderTokenName = "__RequestVerificationToken";
public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
{
if (actionContext == null)
{
throw new ArgumentNullException("actionContext");
}
var headers = actionContext.Request.Headers;
IEnumerable<string> tokens;
try
{
if (headers.TryGetValues(HeaderTokenName, out tokens))
{
var headerToken = tokens.FirstOrDefault();
var cookie = headers.GetCookies().Select(c => c[AntiForgeryConfig.CookieName]).FirstOrDefault();
var cookieToken = cookie != null ? cookie.Value : null;
AntiForgery.Validate(cookieToken, headerToken);
}
else
{
string msg = HttpStatusCode.ExpectationFailed.ToString() + " - " + HeaderTokenName;
throw new InvalidOperationException(msg);
}
}
catch
{
actionContext.Response = new HttpResponseMessage
{
RequestMessage = actionContext.ControllerContext.Request,
StatusCode = HttpStatusCode.Forbidden,
ReasonPhrase = "Token 값이 없습니다."
};
}
base.OnActionExecuting(actionContext);
}
}
구현한 filter 에 대해 아래와 같이 atrribute 를 명시해서 실행합니다. 문제 없으면 아래 함수의 내용을 실행하겠죠.
[AllowAnonymous]
[Route("api/Login")]
[ValidateAjaxAntiForgeryToken]
public async Task<bool> LoginUser()
{
}