การเรียก jQuery Ajax และ Html.AntiForgeryToken ()


207

ฉันได้ติดตั้งแอพพลิเคชั่นเพื่อลดการโจมตี CSRFจากข้อมูลที่ฉันได้อ่านในบล็อกโพสต์รอบอินเทอร์เน็ต โดยเฉพาะอย่างยิ่งโพสต์เหล่านี้เป็นตัวขับเคลื่อนของการติดตั้งของฉัน

โดยทั่วไปบทความและคำแนะนำเหล่านั้นบอกว่าเพื่อป้องกันการโจมตี CSRF ใคร ๆ ก็ควรใช้รหัสต่อไปนี้

1) เพิ่ม[ValidateAntiForgeryToken]ในทุกการกระทำที่ยอมรับกริยา POST Http

[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>

อย่างไรก็ตามในบางส่วนของแอพของฉันฉันกำลังทำ Ajax POST กับ jQuery ไปยังเซิร์ฟเวอร์โดยไม่ต้องมีฟอร์มใด ๆ เลย สิ่งนี้เกิดขึ้นเช่นที่ฉันอนุญาตให้ผู้ใช้คลิกที่ภาพเพื่อดำเนินการบางอย่าง

สมมติว่าฉันมีตารางพร้อมรายการกิจกรรม ฉันมีภาพในคอลัมน์ของตารางที่ระบุว่า "ทำเครื่องหมายกิจกรรมเป็นเสร็จสมบูรณ์" และเมื่อผู้ใช้คลิกที่กิจกรรมนั้นฉันกำลังทำ 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 หรือไม่

ขออภัยสำหรับการโพสต์ยาวและขอบคุณมากที่ช่วยออก

แก้ไข :

ตามคำตอบ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) {
            // ....
        }
    });
});

ลิงค์เดวิดเฮย์เดนตอนนี้ 404 ก็ปรากฏว่าเขาอพยพบล็อกของเขากับ CMS ใหม่ แต่ไม่ได้ย้ายเนื้อหาทั้งหมดที่มีอายุมากกว่า

คำตอบ:


252

ฉันใช้ฟังก์ชั่น 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>  

จากนั้นในการโทร ajax ของคุณ (แก้ไขเพื่อให้ตรงกับตัวอย่างที่สองของคุณ)

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

6
ดีฉันชอบการห่อหุ้มของการดึงโทเค็น
jball

2
@ Lorenzo ใส่ข้อมูลที่กำหนดเองของคุณไว้ในการโทรAddAntiForgeryTokenเช่น:data: AddAntiForgeryToken({ id: parseInt($(this).attr("title")) }),
jball

3
ความคิดที่เลวร้ายเพียงใดที่จะใช้ajaxSendหรือแทนที่ajaxเพื่อเพิ่มdataโทเค็นต่อต้านการปลอมแปลงเสมอ อาจเพิ่มการตรวจสอบบางอย่างเพื่อให้แน่ใจว่าสิ่งที่urlกำหนดไว้สำหรับเซิร์ฟเวอร์ของคุณ
ta.speot.is

1
ระวังถ้าคุณใช้แคชเอาต์พุต
Barbaros Alp

1
@SouhaiebBesbes โทเค็นการตรวจสอบความถูกต้องควรเหมือนกันสำหรับผู้ใช้ในทุกหน้า (ทำงานร่วมกับคุกกี้ที่ตั้งค่าและคงไว้เหมือนเดิม) ดังนั้นจึงไม่สำคัญว่าจะมีหลายคำขอต่อหน้าหรือไม่หากเป็นหน้าฐานจะโหลดซ้ำ
JeremyWeir

29

ฉันชอบโซลูชันที่ให้บริการโดย 360Airwalk แต่อาจได้รับการปรับปรุงเล็กน้อย

ปัญหาแรกคือถ้าคุณทำ$.post()กับข้อมูลว่างเปล่า jQuery จะไม่เพิ่มContent-Typeส่วนหัวและในกรณีนี้ 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);
            }
        }
    });
});

1
+1 คุณถูกต้องฉันไม่เคยนึกถึงปัญหาการโพสต์การโทรที่ว่างเปล่า ขอบคุณสำหรับการป้อนข้อมูล คุณพูดถูกว่าเราไม่ได้ใช้การลบ / วางในโครงการของเรา
360Airwalk

2
+1 สำหรับการบันทึกฉันจากการไม่ต้องเพิ่มฟังก์ชั่นการโทร jQuery.Ajax ทั้งหมด
Dragos Durlut

2
+1 เช่นเดียวกับบันทึกสำหรับลูกหลานเอกสาร jQuery สำหรับ.ajaxSend()รัฐ "ตั้งแต่ jQuery 1.8 ควรแนบเมธอด. jaxSend () กับเอกสารเท่านั้น" api.jquery.com/ajaxsend
RJ Cuthbertson

1
@Bronx optionsมาจากไหนซึ่งอยู่ในifงบสุดท้าย? ขอบคุณ
hvaughan3

ระวังการใช้สิ่งนี้หากคุณมีหลายรูปแบบในหน้า คุณจะต้องตั้งค่าเป็น beforeSend ด้วยตัวเลือกการโทรเฉพาะเจาะจงมากกว่าสำหรับเอกสาร
ด่าน

22

ฉันรู้ว่ามีคำตอบอื่น ๆ อีกมากมาย แต่บทความนี้ดีและกระชับและบังคับให้คุณตรวจสอบ 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 () {
        ...
    }
});

4
แอตทริบิวต์จากบทความที่คุณเชื่อมโยงเข้าด้วยกันรวมกับการตอบสนองของ Bronxคือทางออก DRY ที่ดีที่สุดสำหรับปัญหานี้
TugboatCaptain

2
หาที่ดี ฉันแก้ไขคำตอบของคุณเพื่อรวมตัวอย่างโค้ดเพื่อให้คำตอบเป็นของตัวเอง แต่ฉันหวังว่าผู้คนจะอ่านบทความที่เหลือด้วยเช่นกัน นี่ดูเหมือนจะเป็นทางออกที่สะอาดมาก
Tim Medora

ขอบคุณทิมมันเป็นความคิดที่ยอดเยี่ยมมันน่าผิดหวังเมื่อลิงค์หายไปและคำตอบก็ไร้ค่า ฉันเริ่มทำสิ่งนี้กับคำตอบใหม่ทั้งหมดของฉัน
viggity

MVC นี้เป็น WebAPI หรือ. NetCore หรือไม่ ฉันไม่สามารถรับเนมสเปซที่ถูกต้องสำหรับ WebAPI 5
Myster

20

ฉันรู้สึกเหมือนหมอผีขั้นสูงที่นี่ แต่นี่ก็ยังคงเป็นปัญหาในอีก 4 ปีต่อมาใน MVC5

ในการจัดการคำขอ ajax อย่างถูกต้องโทเค็นป้องกันการปลอมแปลงจะต้องถูกส่งผ่านไปยังเซิร์ฟเวอร์ในการโทร ajax การรวมเข้ากับข้อมูลโพสต์และโมเดลของคุณนั้นยุ่งและไม่จำเป็น การเพิ่มโทเค็นเป็นส่วนหัวที่กำหนดเองนั้นสะอาดและนำกลับมาใช้ใหม่ได้ - และคุณสามารถกำหนดค่าเพื่อให้คุณไม่ต้องจำว่าต้องทำทุกครั้ง

มีข้อยกเว้นคือ - อาแจ็กซ์ที่ไม่สร้างความรำคาญไม่ต้องการการดูแลเป็นพิเศษสำหรับการโทรอาแจ็กซ์ โทเค็นจะถูกส่งตามปกติในฟิลด์อินพุตปกติที่ซ่อนอยู่ เหมือนกับ POST ทั่วไป

_Layout.cshtml

ใน _layout.cshtml ฉันมีบล็อก JavaScript นี้ มันไม่ได้เขียนโทเค็นลงใน DOM แต่จะใช้ jQuery เพื่อแยกออกจากตัวอักษรอินพุตที่ซ่อนที่ MVC Helper สร้างขึ้น สตริง Magic ที่เป็นชื่อส่วนหัวถูกกำหนดเป็นค่าคงที่ในคลาสของแอตทริบิวต์

<script type="text/javascript">
    $(document).ready(function () {
        var isAbsoluteURI = new RegExp('^(?:[a-z]+:)?//', 'i');
        //http://stackoverflow.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 แต่จัดการกับ ajax ที่ไม่เป็นการรบกวนได้อย่างถูกต้อง รหัสนี้สามารถซ่อนอยู่ในห้องสมุดทั่วไปของคุณ

[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);
            }
        }
    }
}

เซิร์ฟเวอร์ / คอนโทรลเลอร์

ตอนนี้คุณเพียงแค่ใช้แอททริบิวกับ Action ของคุณ ยิ่งไปกว่านั้นคุณสามารถใช้แอททริบิวกับคอนโทรลเลอร์ของคุณและคำขอทั้งหมดจะได้รับการตรวจสอบความถูกต้อง

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

โซลูชั่นที่สมบูรณ์แบบรวมศูนย์มากขึ้น ขอบคุณ
David Freire

คุณสามารถอธิบายรายละเอียดเพิ่มเติมว่าทำไมคุณจึงต้องการเพิ่มส่วนหัวสำหรับ URL สัมพัทธ์เท่านั้น นั่นไปเหนือหัวของฉัน สุดยอดทางออก!
MattM

สัมพัทธ์ช่วยให้มั่นใจได้ว่าส่วนหัวจะถูกตั้งค่าตามคำขอที่จะกลับไปที่เซิร์ฟเวอร์ของคุณเองเนื่องจากการตั้งค่า ajax ครอบคลุมการร้องขอทั้งหมดที่ทำด้วย jquery เราไม่ต้องการให้โทเค็นถูกส่งไปยังคำขอ jsonp หรือ CORS สิ่งนี้อาจเป็นจริงสำหรับ URL ที่แน่นอนเช่นกัน แต่ความสัมพันธ์จะรับประกันว่าเป็นโดเมนเดียวกัน
D

1
@ WillD ฉันชอบโซลูชันของคุณ แต่ถูกบังคับให้แก้ไขเล็กน้อย เนื่องจากคุณเลือกที่$.ajaxSetupจะกำหนดตัวbeforesendจัดการเหตุการณ์ทั่วไปมันสามารถเกิดขึ้นได้ที่คุณเขียนทับมัน ฉันพบวิธีแก้ไขปัญหาอื่นที่คุณสามารถเพิ่มตัวจัดการที่สองซึ่งจะถูกเรียก ทำงานได้ดีและไม่ทำให้การใช้งานของคุณเสียหาย
Viper

ไม่มีใครมีรุ่น ASP.net 5 ของลูกค้าที่ตรวจสอบคุณสมบัติ AntiForgery? รุ่นนี้ไม่ได้รวบรวมในเวอร์ชั่นล่าสุด!
Rob McCabe

19

อย่าใช้Html.AntiForgeryToken ใช้AntiForgery.GetTokensและAntiForgery.Validateจาก Web API แทนดังที่อธิบายไว้ในการป้องกันการโจมตีการปลอมแปลงคำร้องขอข้ามไซต์ (CSRF) ในแอปพลิเคชัน ASP.NET MVCแทน


สำหรับวิธีการดำเนินการควบคุมที่รุ่นผูกประเภทรูปแบบเซิร์ฟเวอร์กับ AJAX JSON โพสต์มีประเภทเนื้อหาเป็น "application / json" เป็นสิ่งจำเป็นสำหรับเครื่องผูกรุ่นที่เหมาะสมที่จะใช้ น่าเสียดายที่สิ่งนี้ขัดขวางการใช้ข้อมูลแบบฟอร์มซึ่งเป็นที่ต้องการโดยแอตทริบิวต์ [ValidateAntiForgeryToken] ดังนั้นวิธีการของคุณจึงเป็นวิธีเดียวที่ฉันจะสามารถทำให้มันทำงานได้ คำถามเดียวของฉันคือมันยังทำงานในเว็บฟาร์มหรืออินสแตนซ์บทบาทเว็บ Azure หลายอินสแตนซ์หรือไม่ @Edward คุณหรือคนอื่นรู้ว่านี่เป็นปัญหาหรือไม่?
Richard B

@Edward Brey คุณช่วยอธิบายเพิ่มเติมเกี่ยวกับสาเหตุที่เราไม่ควรใช้มันได้หรือไม่?
Odys

4
@Odys: ไม่มีอะไรผิดปกติกับ Html.AntiForgeryToken แต่มีข้อเสีย: ต้องการแบบฟอร์มต้องการ jQuery และถือว่า Html.AntiForgeryToken ที่ไม่มีเอกสารประกอบ ถึงกระนั้นก็ดีในหลายบริบท คำสั่งของฉัน "อย่าใช้ Html.AntiForgeryToken" อาจจะรุนแรงเกินไป ความหมายของฉันคือมันไม่ได้ตั้งใจที่จะใช้กับ Web API ในขณะที่ AntiForgery.GetTokens มีความยืดหยุ่นมากขึ้น
Edward Brey

ขอบคุณ! ฉันต้องเปลี่ยนนิดหน่อยเพื่อให้มันทำงานกับคอนโทรลเลอร์ MVC5 แต่นี่เป็นวิธีแก้ปัญหา
jao

3
แน่นอนมันไม่จำเป็นต้องมีแบบฟอร์ม คุณเพียงแค่ต้องแยก DOM สำหรับมันโดยใช้ชื่อ ใช้ jquery ฉันสามารถเพิ่มไว้ในวัตถุข้อมูลของฉันผ่านข้อมูล {__RequestVerificationToken: $ ("อินพุต [ชื่อ = __ RequestVerificationToken]"). val ()}
Anthony Mason

16

ฉันเพิ่งใช้ปัญหาจริงนี้ในโครงการปัจจุบันของฉัน ฉันทำเพื่อ ajax-POST ทั้งหมดที่ต้องการผู้ใช้ที่ผ่านการรับรองความถูกต้อง

ก่อนอื่นฉันตัดสินใจที่จะขอให้ ajax jquery ของฉันดังนั้นฉันจะไม่พูดซ้ำบ่อยเกินไป ตัวอย่างจาวาสคริปต์นี้ทำให้มั่นใจได้ว่าการโทร ajax ทั้งหมด (โพสต์) จะเพิ่มโทเค็นการตรวจสอบคำขอของฉันให้กับคำขอ หมายเหตุ: ชื่อ __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-Helper ทั่วไป โดยทั่วไปคุณสามารถเพิ่มรหัสนี้ได้ทุกที่ที่คุณต้องการ ฉันวางไว้ภายในคำสั่ง 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 มาตรฐาน ฉันทำแบบนี้ (แม้ว่าฉันจะใช้ Salt จริง ๆ )

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

ด้วย Firebug หรือเครื่องมือที่คล้ายกันคุณสามารถดูว่าคำขอ POST ของคุณในตอนนี้มีพารามิเตอร์ __RequestVerificationToken ต่อท้ายอย่างไร


15

ฉันคิดว่าสิ่งที่คุณต้องทำคือตรวจสอบให้แน่ใจว่าอินพุต "__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) {
            // ....
        }
    });
});

1
หลังจากผ่านไปหลายชั่วโมงทดลองกับการโพสต์ jQuery AJAX จากภายในหน้า MVC (มีดโกน) นี่เป็นคำตอบที่ง่ายที่สุดสำหรับฉัน เพียงแค่รวมเขตข้อมูลของคุณเอง (หรือมุมมองฉันคิดว่า modelModel) หลังจากโทเค็นเป็นชิ้นส่วนของข้อมูลใหม่ (แต่ภายในวัตถุข้อมูลต้นฉบับ)
Ralph Bacon

ฉันจะใช้สิ่งนี้ได้อย่างไรถ้าฟังก์ชั่น AJAX อยู่ในหน้า. html และไม่ใช่หน้ามีดโกน
Bob the Builder

หากหน้า html ของคุณไม่มีเซิร์ฟเวอร์ที่ให้มาAntiForgeryTokenนั่นก็คือ moot ทั้งหมด หากไม่เป็นเช่นนั้น (ไม่แน่ใจว่าคุณได้รับมาอย่างไรในกรณีนั้น แต่สมมติว่าคุณเป็นเช่นนั้น) สิ่งที่กล่าวมาข้างต้นจะใช้ได้ดี หากคุณกำลังพยายามสร้างหน้าเว็บแบบง่ายๆที่จะโพสต์คำขอไปยังเซิร์ฟเวอร์ที่คาดว่าโทเค็นพูดแล้วและเซิร์ฟเวอร์ไม่ได้สร้างเพจดังกล่าวแสดงว่าคุณไม่มีโชค นั่นเป็นหลักจุด AntiForgeryToken และ ...
jball

6

คุณสามารถทำได้เช่นกัน:

$("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ไวยากรณ์คุณก็สามารถใช้<%= %>แท็กได้เช่นกัน


4

นอกจากความคิดเห็นของฉันที่มีต่อคำตอบของ @ JBall ที่ช่วยฉันไปตลอดทางนี่เป็นคำตอบสุดท้ายที่เหมาะกับฉัน ฉันใช้ MVC และมีดโกนและฉันส่งแบบฟอร์มโดยใช้ jQuery AJAX เพื่อให้ฉันสามารถอัปเดตมุมมองบางส่วนด้วยผลลัพธ์ใหม่บางส่วนและฉันไม่ต้องการทำ postback ที่สมบูรณ์ (และการกระเพื่อมหน้า)

เพิ่ม @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 ที่พิมพ์แบบอ่อน) นี่ก็โอเคฉันคิดว่า หากคุณทราบดีกว่าโปรดแจ้งให้เราทราบตามที่ฉันต้องการสร้างรหัส MVC C # ที่มีประสิทธิภาพ


4

พบแนวคิดที่ฉลาดมากจากhttps://gist.github.com/scottrippey/3428114สำหรับทุก ๆ $ .ajax เรียกว่าแก้ไขคำขอและเพิ่มโทเค็น

// 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();
        }
    }
});

ฉันลองตัวเลือกอื่นหลายตัวด้านบนนี่คือสิ่งที่แก้ไขได้สำหรับฉัน
HostMyBus

อย่างไรก็ตามฉันต้องเพิ่มif (options.contentType != false && options.contentType.indexOf('application/json') === 0) {เพื่อจับการโทร Ajax ที่ไม่ได้ระบุประเภทเนื้อหา
HostMyBus

3

1. กำหนดฟังก์ชันเพื่อรับโทเค็นจากเซิร์ฟเวอร์

@function
{

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

2. รับโทเค็นและตั้งค่าส่วนหัวก่อนส่งไปยังเซิร์ฟเวอร์

var token = '@TokenHeaderValue()';    

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

3. การตรวจสอบความถูกต้องของผู้ใช้บน HttpRequestBase ตามวิธีการที่คุณจัดการกับ 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);

1

ฉันรู้ว่ามีเวลาพอสมควรตั้งแต่คำถามนี้ถูกโพสต์ แต่ฉันพบทรัพยากรที่มีประโยชน์จริง ๆ ซึ่งกล่าวถึงการใช้ AntiForgeryToken และทำให้การใช้งานลำบากน้อยลง นอกจากนี้ยังมีปลั๊กอิน jquery เพื่อให้ง่ายต่อการรวมโทเค็น antiforgery ในการโทร AJAX:

สูตรคำขอป้องกันการปลอมแปลงสำหรับ ASP.NET MVC และ AJAX

ฉันไม่ได้มีส่วนช่วยอะไรมาก แต่อาจมีบางคนที่เห็นว่ามีประโยชน์


โพสต์นั้นยาวประมาณหนึ่งไมล์! ฉันแน่ใจว่ามันยอดเยี่ยม แต่ tl; dr
พัฒนาชาวอังกฤษ

1
แย่มากเพราะมันครอบคลุมเรื่องอย่างมาก ไม่เพียงบอกวิธีการใช้คุณสมบัตินี้ แต่อธิบายถึงปัญหาที่แก้ไขและให้บริบทเพื่อให้คุณเข้าใจวิธีการใช้งานอย่างถูกต้อง เมื่อพูดถึงเรื่องความปลอดภัยฉันคิดว่าความเข้าใจในเชิงลึกเป็นสิ่งสำคัญ
slawek

2
หากเป็นสิ่งสำคัญควรเขียนในลักษณะที่กระตุ้นให้ผู้คนอ่านมัน)
พัฒนาชาวอังกฤษ

1

ใช้งานครั้งแรก @ Html.AntiForgeryToken () เป็น html

 $.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);
        }

1

นี่คือวิธีที่ง่ายที่สุดที่ฉันเคยเห็น หมายเหตุ: ตรวจสอบให้แน่ใจว่าคุณมี "@ 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
        });
    });

0

การปรับปรุงเล็กน้อยเป็นโซลูชัน 360Airwalk นี่เป็นการเพิ่ม Anti Tokery Token ภายในฟังก์ชัน javascript ดังนั้น @ 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);
            }
        }
    });
});

0
function DeletePersonel(id) {

    var data = new FormData();
    data.append("__RequestVerificationToken", "@HtmlHelper.GetAntiForgeryToken()");

    $.ajax({
        type: 'POST',
        url: '/Personel/Delete/' + id,
        data: data,
        cache: false,
        processData: false,
        contentType: false,
        success: function (result) {
        }
    });
}

public static class HtmlHelper {
    public static string GetAntiForgeryToken() {
        System.Text.RegularExpressions.Match value = 
                System.Text.RegularExpressions.Regex.Match(System.Web.Helpers.AntiForgery.GetHtml().ToString(), 
                        "(?:value=\")(.*)(?:\")");
        if (value.Success) {
            return value.Groups[1].Value;
        }
        return "";
    }
}

0

ฉันใช้ ajax post เพื่อเรียกใช้วิธีการลบ (เกิดจากไทม์ไลน์ของ visjs แต่นั่นไม่ได้เกี่ยวข้อง) นี่คือสิ่งที่ฉัน sis:

นี่คือ 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 ของฉันฉันใช้:

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

ซึ่งจะเพิ่มค่าโทเค็นคัดลอกหน้าไปยังฟิลด์ที่โพสต์

ก่อนหน้านี้ฉันลองใส่ค่าในส่วนหัว แต่ฉันได้รับข้อผิดพลาดเดียวกัน

โพสต์การปรับปรุง นี่ดูเหมือนจะเป็นวิธีง่ายๆที่ฉันสามารถเข้าใจได้


0

มีจำนวนโพสต์ที่นี่ไม่มีใครช่วยฉันวันและวันที่ของ google และยังไม่มีอีกต่อไปที่ฉันได้ไปถึงจุดที่การเขียนแอปทั้งหมดตั้งแต่เริ่มต้นและจากนั้นฉันสังเกตเห็นนักเก็ตตัวน้อยใน Web.confg

 <httpCookies requireSSL="false" domain="*.localLookup.net"/>

ตอนนี้ฉันไม่รู้ว่าทำไมฉันจึงเพิ่มมัน แต่ฉันสังเกตเห็นตั้งแต่นั้นมามันเพิกเฉยในโหมดแก้ไขข้อบกพร่องและไม่ได้อยู่ในโหมดการผลิต (IE ติดตั้งไปยัง IIS บางแห่ง)

สำหรับฉันการแก้ปัญหาคือหนึ่งใน 2 ตัวเลือกเนื่องจากฉันจำไม่ได้ว่าทำไมฉันจึงเพิ่มมันฉันไม่สามารถแน่ใจได้ว่าสิ่งอื่น ๆ ไม่ขึ้นอยู่กับและที่สองชื่อโดเมนจะต้องเป็นกรณีที่ต่ำกว่าและ TLD ไม่ชอบ ive ใน * .localLookup.net

บางทีมันอาจช่วยไม่ได้ ฉันหวังว่ามันจะช่วยให้ใครบางคน


0

โซลูชันที่ฉันพบไม่ใช่สำหรับ ASPX แต่สำหรับมีดโกน แต่เป็นปัญหาที่สามารถบังคับได้

ฉันแก้ไขมันโดยเพิ่ม AntiForgery ให้กับคำขอ ตัวช่วยเหลือ HTML ไม่ได้สร้างรหัส HTML ด้วยการโทร

@Html.AntiForgeryToken()

เพื่อที่จะเพิ่มโทเค็นลงใน postrequest ฉันเพิ่งเพิ่มรหัส AntiForgery ไปยังเขตข้อมูลที่ซ่อนด้วย jquery:

$("input[name*='__RequestVerificationToken']").attr('id', '__AjaxAntiForgeryForm');

สิ่งนี้ทำให้คอนโทรลเลอร์ยอมรับการร้องขอด้วยแอตทริบิวต์ [ValidateAntiForgeryToken]


-3

AntiforgeryToken ยังคงเป็นความเจ็บปวดไม่มีตัวอย่างด้านบนที่ทำงานเพื่อฉัน มีมากเกินไปสำหรับที่นั่น ดังนั้นฉันรวมพวกเขาทั้งหมด ต้องการ @ Html.AntiforgeryToken ในรูปแบบที่ห้อยอยู่รอบ ๆ iirc

แก้ไขได้เช่น:

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

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

เมื่อมีข้อสงสัยเพิ่มสัญญาณ $ เพิ่มเติม

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.