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




asp.net-mvc-2 csrf (16)

필자는 인터넷에서 블로그 게시물을 통해 읽은 정보에 이어 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) {
            // ....
        }
    });
});

Answers

360Airwalk 솔루션이 약간 향상되었습니다. 이것은 javascript 함수 내에서 Anti Whery Token을 imbed하므로 @ Html.AntiForgeryToken ()이 더 이상 모든 뷰에 포함될 필요가 없습니다.

$(document).ready(function () {
    var securityToken = $('@Html.AntiForgeryToken()').attr('value');
    $('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);
            }
        }
    });
});

여기 내가 본 가장 쉬운 방법이 있습니다. 참고 :보기에 "@ 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
        });
    });

AntiforgeryToken은 여전히 ​​고통 스럽습니다. 위의 예제 중 아무 것도 나를 위해 일하는 단어가 아닙니다. 거기에 너무 많아. 그래서 나는 그들 모두를 결합했다. iirc 주위에 걸려있는 형태로 @ Html.AntiforgeryToken이 필요합니다.

해결 방법 :

function Forgizzle(eggs) {
    eggs.__RequestVerificationToken =  $($("input[name=__RequestVerificationToken]")[0]).val();
    return eggs;
}

$.ajax({
            url: url,
            type: 'post',
            data: Forgizzle({ id: id, sweets: milkway }),
});

의심스러운 경우 더 많은 $ 표시를 추가하십시오.


모든 $ .ajax 호출에 대해 https://gist.github.com/scottrippey/3428114 에서이 아주 똑똑한 아이디어를 발견했습니다. 요청을 수정하고 토큰을 추가합니다.

// Setup CSRF safety for AJAX:
$.ajaxPrefilter(function(options, originalOptions, jqXHR) {
    if (options.type.toUpperCase() === "POST") {
        // We need to add the verificationToken to all POSTs
        var token = $("input[name^=__RequestVerificationToken]").first();
        if (!token.length) return;

        var tokenName = token.attr("name");

        // If the data is JSON, then we need to put the token in the QueryString:
        if (options.contentType.indexOf('application/json') === 0) {
            // Add the token to the URL, because we can't add it to the JSON data:
            options.url += ((options.url.indexOf("?") === -1) ? "?" : "&") + token.serialize();
        } else if (typeof options.data === 'string' && options.data.indexOf(tokenName) === -1) {
            // Append to the data string:
            options.data += (options.data ? "&" : "") + token.serialize();
        }
    }
});

나는 다른 많은 답변이 있다는 것을 알고 있지만,이 기사는 훌륭하고 간결하며, 당신이 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 () {
        ...
    }
});

나는 이처럼 간단한 js 함수를 사용한다.

AddAntiForgeryToken = function(data) {
    data.__RequestVerificationToken = $('#__AjaxAntiForgeryForm input[name=__RequestVerificationToken]').val();
    return data;
};

페이지의 모든 양식은 토큰에 대해 동일한 값을 가지므로 최상단 마스터 페이지에 다음과 같이 입력하십시오

<%-- used for ajax in AddAntiForgeryToken() --%>
<form id="__AjaxAntiForgeryForm" action="#" method="post"><%= Html.AntiForgeryToken()%></form>  

그런 다음 아약스 호출 do (두 번째 예제와 일치하도록 편집 됨)

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


"__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);
            }
        }
    });
});

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

$("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 구문을 사용하는 경우 <%= %> 태그를 사용할 수도 있습니다


이 질문이 게시 된 지 얼마되지 않아서 알았지 만, AntiForgeryToken의 사용법을 설명하고 사용하기가 덜 수월한 유용한 리소스를 발견했습니다. 또한 AJAX 호출에서 위조 방지 토큰을 쉽게 포함 할 수있는 jquery 플러그인을 제공합니다.

ASP.NET MVC 및 AJAX 용 위조 방지 요청 레시피

나는별로 기여하지 않지만 어쩌면 누군가가 유용하다고 생각할 것입니다.


삭제 메소드를 실행하기 위해 아약스 게시물을 사용하고 있습니다 (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()
              }
     }
);

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

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

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


@ 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 # 코드를 생성하고자 할 때 알려 주시기 바랍니다.


나는 여기에 고급 사냥꾼처럼 느껴지지만, 이것은 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);
}

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

@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);

나는 이것을 어딘가에서 발견했다. 비록 에 아마도 ... 기억이 안나요 :)

$.fn.serializeObject = function(){
    var o = {};
    var a = this.serializeArray();
    $.each(a, function() {
        if (o[this.name]) {
            if (!o[this.name].push) {
                o[this.name] = [o[this.name]];
            }
            o[this.name].push(this.value || '');
        } else {
            o[this.name] = this.value || '';
        }
    });
    return o;
};




asp.net-mvc ajax asp.net-mvc-2 csrf antiforgerytoken