asp.net mvc - jQuery Ajax 호출과 Html.AntiForgeryToken()




asp.net-mvc asp.net-mvc-2 (12)

필자는 인터넷에서 블로그 게시물을 통해 읽은 정보에 이어 CSRF 공격에 대한 완화책을 구현했습니다. 특히이 게시물은 내 구현의 원동력이었습니다.

기본적으로 그 기사들과 권고들은 CSRF 공격을 막기 위해 누구나 다음 코드를 구현해야한다고 말한다 :

1) POST Http 동사를 허용하는 모든 작업에 [ValidateAntiForgeryToken] 을 추가합니다

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult SomeAction( SomeModel model ) {
}

2) 서버에 데이터를 제출하는 양식 내에 <%= Html.AntiForgeryToken() %> 도우미를 추가하십시오

<div style="text-align:right; padding: 8px;">
    <%= Html.AntiForgeryToken() %>
    <input type="submit" id="btnSave" value="Save" />
</div>

어쨌든 내 애플 리케이션의 일부에서 전혀 양식을하지 않고 서버에 jQuery와 아약스 POST를하고있다. 이것은 예를 들어 사용자가 이미지를 클릭하여 특정 작업을 수행하게하는 경우에 발생합니다.

나는 활동 목록이있는 테이블을 가지고 있다고 가정한다. 테이블의 열에 "완료된 활동으로 표시"라는 이미지가 있고 사용자가 해당 활동을 클릭하면 다음 샘플에서와 같이 Ajax POST를 수행 중입니다.

$("a.markAsDone").click(function (event) {
    event.preventDefault();
    $.ajax({
        type: "post",
        dataType: "html",
        url: $(this).attr("rel"),
        data: {},
        success: function (response) {
            // ....
        }
    });
});

이 경우 <%= Html.AntiForgeryToken() %> 을 어떻게 사용할 수 있습니까? Ajax 호출의 data 매개 변수 내에 도우미 호출을 포함해야합니까?

긴 게시물에 대해 죄송하며 도움을 주신 것에 대해 대단히 감사합니다.

편집 :

jayrdub 답변 당 나는 다음과 같은 방식으로 사용했습니다

$("a.markAsDone").click(function (event) {
    event.preventDefault();
    $.ajax({
        type: "post",
        dataType: "html",
        url: $(this).attr("rel"),
        data: {
            AddAntiForgeryToken({}),
            id: parseInt($(this).attr("title"))
        },
        success: function (response) {
            // ....
        }
    });
});

먼저 HTML에서 @ Html.AntiForgeryToken ()을 사용하십시오.

 $.ajax({
        url: "@Url.Action("SomeMethod", "SomeController")",
        type: 'POST',
        data: JSON.stringify(jsonObject),
        contentType: 'application/json; charset=utf-8',
        dataType: 'json',
        async: false,
        beforeSend: function (request) {
            request.setRequestHeader("RequestVerificationToken", $("[name='__RequestVerificationToken']").val());
        },
        success: function (msg) {
            alert(msg);
        }

"__RequestVerificationToken"입력이 POST 요청에 포함되어 있는지 확인하기 만하면됩니다. 정보의 나머지 절반 (즉, 사용자의 쿠키에있는 토큰)은 이미 AJAX POST 요청과 함께 자동으로 전송됩니다.

예를 들어,

$("a.markAsDone").click(function (event) {
    event.preventDefault();
    $.ajax({
        type: "post",
        dataType: "html",
        url: $(this).attr("rel"),
        data: { 
            "__RequestVerificationToken":
            $("input[name=__RequestVerificationToken]").val() 
        },
        success: function (response) {
            // ....
        }
    });
});

360Airwalk에서 제공하는 솔루션이 마음에 들지만 조금 개선 될 수 있습니다.

첫 번째 문제는 비어있는 데이터로 $.post() 를 만들면 jQuery가 Content-Type 헤더를 추가하지 $.post() 경우 ASP.NET MVC가 토큰을 받고 확인하지 못한다는 것입니다. 따라서 헤더가 항상 존재하는지 확인해야합니다.

또 다른 개선점은 내용 이있는 모든 HTTP 동사 (POST, PUT, DELETE 등)를 지원하는 것입니다. 응용 프로그램에서 POST 만 사용할 수도 있지만 일반 솔루션을 사용하고 동사와 함께 수신하는 모든 데이터가 위조가 없음을 확인하는 것이 좋습니다 토큰.

$(document).ready(function () {
    var securityToken = $('[name=__RequestVerificationToken]').val();
    $(document).ajaxSend(function (event, request, opt) {
        if (opt.hasContent && securityToken) {   // handle all verbs with content
            var tokenParam = "__RequestVerificationToken=" + encodeURIComponent(securityToken);
            opt.data = opt.data ? [opt.data, tokenParam].join("&") : tokenParam;
            // ensure Content-Type header is present!
            if (opt.contentType !== false || event.contentType) {
                request.setRequestHeader( "Content-Type", opt.contentType);
            }
        }
    });
});

@ JBall의 답변에 대한 제 의견에 더 나아가서 저를 도왔습니다. 이것이 저에게 맞는 최종 답입니다. MVC와 Razor를 사용하고 있고 일부 새로운 결과로 부분 뷰를 업데이트 할 수있는 jQuery AJAX를 사용하여 양식을 제출하고 있으며 전체 포스트 백 (및 페이지 깜박임)을 원하지 않았습니다.

평소와 같이 @Html.AntiForgeryToken() 폼에 추가하십시오.

내 AJAX 제출 버튼 코드 (예 : onclick 이벤트)는 다음과 같습니다.

//User clicks the SUBMIT button
$("#btnSubmit").click(function (event) {

//prevent this button submitting the form as we will do that via AJAX
event.preventDefault();

//Validate the form first
if (!$('#searchForm').validate().form()) {
    alert("Please correct the errors");
    return false;
}

//Get the entire form's data - including the antiforgerytoken
var allFormData = $("#searchForm").serialize();

// The actual POST can now take place with a validated form
$.ajax({
    type: "POST",
    async: false,
    url: "/Home/SearchAjax",
    data: allFormData,
    dataType: "html",
    success: function (data) {
        $('#gridView').html(data);
        $('#TestGrid').jqGrid('setGridParam', { url: '@Url.Action("GetDetails", "Home", Model)', datatype: "json", page: 1 }).trigger('reloadGrid');
    }
});

MvcJqGrid가 포함 된 부분 뷰가 어떻게 업데이트되고 어떻게 새로 고쳐지는지 (매우 강력한 jqGrid 그리드와 이것에 대한 훌륭한 MVC 래퍼) "성공"액션을 남겼습니다.

내 컨트롤러 메서드는 다음과 같습니다.

    //Ajax SUBMIT method
    [ValidateAntiForgeryToken]
    public ActionResult SearchAjax(EstateOutlet_D model) 
    {
        return View("_Grid", model);
    }

모델로 전체 양식의 데이터를 게시하는 팬이 아니라는 것을 인정해야합니다. 그렇다면이를 수행해야하는 경우 작동하는 한 가지 방법입니다. MVC는 단지 데이터 바인딩을 너무 쉽게 만들어서 16 개의 개별 값 (약하게 타입이 지정된 FormCollection)을 제출하는 것이 아니라 OK입니다. 당신이 더 잘 알고 있다면 강력한 MVC C # 코드를 생성하고자 할 때 알려 주시기 바랍니다.


나는 다른 많은 답변이 있다는 것을 알고 있지만,이 기사는 훌륭하고 간결하며, 당신이 HttpPosts의 일부를 검사하는 것뿐만 아니라 모든 HttpPosts를 검사하도록 강요한다.

http://richiban.wordpress.com/2013/02/06/validating-net-mvc-4-anti-forgery-tokens-in-ajax-requests/

양식 컬렉션을 수정하는 대신 HTTP 헤더를 사용합니다.

섬기는 사람

//make sure to add this to your global action filters
[AttributeUsage(AttributeTargets.Class)]
public class ValidateAntiForgeryTokenOnAllPosts : AuthorizeAttribute
{
    public override void OnAuthorization( AuthorizationContext filterContext )
    {
        var request = filterContext.HttpContext.Request;

        //  Only validate POSTs
        if (request.HttpMethod == WebRequestMethods.Http.Post)
        {
            //  Ajax POSTs and normal form posts have to be treated differently when it comes
            //  to validating the AntiForgeryToken
            if (request.IsAjaxRequest())
            {
                var antiForgeryCookie = request.Cookies[AntiForgeryConfig.CookieName];

                var cookieValue = antiForgeryCookie != null
                    ? antiForgeryCookie.Value 
                    : null;

                AntiForgery.Validate(cookieValue, request.Headers["__RequestVerificationToken"]);
            }
            else
            {
                new ValidateAntiForgeryTokenAttribute()
                    .OnAuthorization(filterContext);
            }
        }
    }
}

고객

var token = $('[name=__RequestVerificationToken]').val();
var headers = {};
headers["__RequestVerificationToken"] = token;

$.ajax({
    type: 'POST',
    url: '/Home/Ajax',
    cache: false,
    headers: headers,
    contentType: 'application/json; charset=utf-8',
    data: { title: "This is my title", contents: "These are my contents" },
    success: function () {
        ...
    },
    error: function () {
        ...
    }
});

나는 여기에 고급 사냥꾼처럼 느껴지지만, 이것은 MVC5에서 4 년 후 여전히 논점입니다.

ajax 요청을 제대로 처리하려면 ajax 호출시 위조 토큰을 서버에 전달해야합니다. 귀하의 게시물 데이터 및 모델에 통합하는 것은 지저분하고 불필요합니다. 토큰을 사용자 정의 헤더로 추가하는 것은 깨끗하고 재사용 할 수 있습니다. 그리고 매번 토큰을 기억할 필요가 없도록 구성 할 수 있습니다.

예외가 있습니다 - 눈에 거슬리지 않는 아약스는 아약스 호출에 특별한 대우가 필요하지 않습니다. 토큰은 일반 숨겨진 입력 필드에서 평소와 같이 전달됩니다. 일반 POST와 정확히 동일합니다.

_Layout.cshtml

_layout.cshtml에서이 자바 스크립트 블록이 있습니다. 토큰을 DOM에 쓰지 않고 jQuery를 사용하여 MVC 도우미가 생성하는 숨겨진 입력 리터럴에서 추출합니다. 머리글 이름 인 Magic 문자열은 특성 클래스에서 상수로 정의됩니다.

<script type="text/javascript">
    $(document).ready(function () {
        var isAbsoluteURI = new RegExp('^(?:[a-z]+:)?//', 'i');
        //http://.com/questions/10687099/how-to-test-if-a-url-string-is-absolute-or-relative

        $.ajaxSetup({
            beforeSend: function (xhr) {
                if (!isAbsoluteURI.test(this.url)) {
                    //only add header to relative URLs
                    xhr.setRequestHeader(
                       '@.ValidateAntiForgeryTokenOnAllPosts.HTTP_HEADER_NAME', 
                       $('@Html.AntiForgeryToken()').val()
                    );
                }
            }
        });
    });
</script>

beforeSend 함수에서 작은 따옴표 사용에주의하십시오. 즉, 렌더링되는 입력 요소는 큰 따옴표를 사용하여 JavaScript 리터럴을 손상시킵니다.

고객 자바 스크립트

위의 beforeSend 함수가 실행되면 AntiForgeryToken이 요청 헤더에 자동으로 추가됩니다.

$.ajax({
  type: "POST",
  url: "CSRFProtectedMethod",
  dataType: "json",
  contentType: "application/json; charset=utf-8",
  success: function (data) {
    //victory
  }
});

서버 라이브러리

비표준 토큰을 처리하려면 사용자 정의 속성이 필요합니다. 이것은 @ viggity의 솔루션을 기반으로하지만 눈에 거슬리는 아약스를 올바르게 처리합니다. 이 코드는 공용 라이브러리에서 벗어날 수 있습니다.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class ValidateAntiForgeryTokenOnAllPosts : AuthorizeAttribute
{
    public const string HTTP_HEADER_NAME = "x-RequestVerificationToken";

    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        var request = filterContext.HttpContext.Request;

        //  Only validate POSTs
        if (request.HttpMethod == WebRequestMethods.Http.Post)
        {

            var headerTokenValue = request.Headers[HTTP_HEADER_NAME];

            // Ajax POSTs using jquery have a header set that defines the token.
            // However using unobtrusive ajax the token is still submitted normally in the form.
            // if the header is present then use it, else fall back to processing the form like normal
            if (headerTokenValue != null)
            {
                var antiForgeryCookie = request.Cookies[AntiForgeryConfig.CookieName];

                var cookieValue = antiForgeryCookie != null
                    ? antiForgeryCookie.Value
                    : null;

                AntiForgery.Validate(cookieValue, headerTokenValue);
            }
            else
            {
                new ValidateAntiForgeryTokenAttribute()
                    .OnAuthorization(filterContext);
            }
        }
    }
}

서버 / 컨트롤러

이제는 액션에 속성을 적용하기 만하면됩니다. 컨트롤러에 특성을 적용하면 모든 요청의 유효성을 검사 할 수 있습니다.

[HttpPost]
[ValidateAntiForgeryTokenOnAllPosts]
public virtual ActionResult CSRFProtectedMethod()
{
  return Json(true, JsonRequestBehavior.DenyGet);
}

나는 현재 프로젝트에서이 실제 문제를 구현하고 있었다. 인증 된 사용자가 필요한 모든 Ajax-POST에 대해이 작업을 수행했습니다.

먼저 jquery ajax 호출을 연결하기로 결정 했으므로 너무 자주 반복하지 않아야합니다. 이 자바 스크립트 스 니펫은 모든 Ajax (post) 호출이 내 요청 유효성 검사 토큰을 요청에 추가하도록합니다. 참고 : __RequestVerificationToken이라는 이름은 .Net 프레임 워크에서 사용되므로 다음과 같이 표준 Anti-CSRF 기능을 활용할 수 있습니다.

$(document).ready(function () {
    var securityToken = $('[name=__RequestVerificationToken]').val();
    $('body').bind('ajaxSend', function (elm, xhr, s) {
        if (s.type == 'POST' && typeof securityToken != 'undefined') {
            if (s.data.length > 0) {
                s.data += "&__RequestVerificationToken=" + encodeURIComponent(securityToken);
            }
            else {
                s.data = "__RequestVerificationToken=" + encodeURIComponent(securityToken);
            }
        }
    });
});

위의 javascript에서 토큰을 사용할 수 있어야하는 뷰에서 일반적인 HTML 도우미를 사용하면됩니다. 기본적으로 원하는 코드를 추가 할 수 있습니다. if (Request.IsAuthenticated) 문 내에 배치했습니다.

@Html.AntiForgeryToken() // you can provide a string as salt when needed which needs to match the one on the controller

컨트롤러에서 표준 ASP.Net MVC Anti-CSRF 메커니즘을 사용하면됩니다. 나는 이것을 (실제로 소금을 사용했지만) 좋아했습니다.

[HttpPost]
[Authorize]
[ValidateAntiForgeryToken]
public JsonResult SomeMethod(string param)
{
    // do something
    return Json(true);
}

Firebug 또는 유사한 도구를 사용하면 POST 요청에 __RequestVerificationToken 매개 변수가 추가 된 방식을 쉽게 볼 수 있습니다.


당신도 이것을 할 수 있습니다 :

$("a.markAsDone").click(function (event) {
    event.preventDefault();

    $.ajax({
        type: "post",
        dataType: "html",
        url: $(this).attr("rel"),
        data: $('<form>@Html.AntiForgeryToken()</form>').serialize(),
        success: function (response) {
        // ....
        }
    });
});

이것은 Razor 를 사용하지만 WebForms 구문을 사용하는 경우 <%= %> 태그를 사용할 수도 있습니다


삭제 메소드를 실행하기 위해 아약스 게시물을 사용하고 있습니다 (visjs 타임 라인에서 발생하지만 그것은 관련성이 없습니다). 이것은 내가 뭘까요?

이것은 내 Index.cshtml입니다.

@Scripts.Render("~/bundles/schedule")
@Styles.Render("~/bundles/visjs")
@Html.AntiForgeryToken()

<!-- div to attach schedule to -->
<div id='schedule'></div>

<!-- div to attach popups to -->
<div id='dialog-popup'></div>

내가 여기에 추가 한 것은 토큰이 페이지에 나타나도록 @Html.AntiForgeryToken() 이었습니다.

그렇다면 아약스 게시물에서 나는 다음을 사용했다.

$.ajax(
    {
        type: 'POST',
        url: '/ScheduleWorks/Delete/' + item.id,
        data: {
            '__RequestVerificationToken': 
            $("input[name='__RequestVerificationToken']").val()
              }
     }
);

게시 된 필드에 페이지에서 긁어 낸 토큰 값을 추가합니다.

전에 헤더에 값을 넣으려고했지만 같은 오류가 발생했습니다.

부담없이 개선 사항을 게시하십시오. 이것은 분명히 내가 이해할 수있는 간단한 접근 방법 인 것처럼 보인다.


여기 내가 본 가장 쉬운 방법이 있습니다. 참고 :보기에 "@ Html.AntiForgeryToken ()"이 있는지 확인하십시오.

  $("a.markAsDone").click(function (event) {
        event.preventDefault();
        var sToken = document.getElementsByName("__RequestVerificationToken")[0].value;
        $.ajax({
            url: $(this).attr("rel"),
            type: "POST",
            contentType: "application/x-www-form-urlencoded",
            data: { '__RequestVerificationToken': sToken, 'id': parseInt($(this).attr("title")) }
        })
        .done(function (data) {
            //Process MVC Data here
        })
        .fail(function (jqXHR, textStatus, errorThrown) {
            //Process Failure here
        });
    });


서버에서 토큰을 가져 오는 기능 정의

@function
{

        public string TokenHeaderValue()
        {
            string cookieToken, formToken;
            AntiForgery.GetTokens(null, out cookieToken, out formToken);
            return cookieToken + ":" + formToken;                
        }
}

서버에 보내기 전에 토큰을 설정하고 헤더를 설정하십시오.

var token = '@TokenHeaderValue()';    

       $http({
           method: "POST",
           url: './MainBackend/MessageDelete',
           data: dataSend,
           headers: {
               'RequestVerificationToken': token
           }
       }).success(function (data) {
           alert(data)
       });

3. 당신이 처리하는 메소드의 HttpRequestBase에 대한 Onserver 유효성 검사 Post / get

        string cookieToken = "";
        string formToken = "";
        string[] tokens = Request.Headers["RequestVerificationToken"].Split(':');
            if (tokens.Length == 2)
            {
                cookieToken = tokens[0].Trim();
                formToken = tokens[1].Trim();
            }
        AntiForgery.Validate(cookieToken, formToken);






antiforgerytoken