C# Razor 页面中无表单 AJAX POST 和 AntiForgery 的正确处理

问题描述 投票:0回答:1

在这个假设的场景中,我在页面上显示消息列表。

一段 javascript -

Edit(id)
- 从服务器检索用于编辑消息的正确 HTML,并将其注入到页面上。

另一个 javascript 片段 -

Save(id)
- 将 AJAX POST 返回到服务器并将结果注入回页面。

根据 Razor Pages 的 AntiForgery 规则,POST 会触发 404,因为它缺少正确的令牌。由于我没有使用

<form>
,内置的防伪框架不会自动启动并提供令牌。

并且,由于理论上我可以选择并行编辑多条消息,因此我不确定如何正确实现

@Html.AntiForgeryToken()
字段。我是为每条消息生成一个令牌,然后以某种方式将其收集起来并与我的 POST 一起发送,还是......?

我想知道是否有一种正确的方法来处理这种情况,考虑到防伪功能,或者我是否必须忍受它并围绕它构建?我可以使用

[IgnoreAntiforgeryToken]
轻松解决这个问题,但这显然有其自身的后果。

暂时,

MessageManager.Messages
只是静态
List<TextMessage>

查看.cshtml

@foreach (var item in Model.Messages) {
    <div id="[email protected]">
        <label class="form-label">@item.Type:</label>
        @{
            var result = Markdown.ToHtml(item.Message, pipeline);
            @Html.Raw(result)
        }
    </div>
    <div class="mb-3 text-end">
        <a href="#" onclick="Edit(@item.Id)">✏</a>
        <a href="#" onclick="Delete(@item.Id)">❌</a>
    </div>
}


@section Scripts {
    <script>
        function Edit(messageId) {
            $.get(
                "/Messages/_EditMessage?messageId=" + messageId,
                function (str) {
                    $('#message_' + messageId).html(str);
                }
            );
        }
        function Save(messageId, content) {
            $.post(
                "/Messages/_EditMessage?messageId=" + messageId,
                { NewMessage: $('#NewMessage_' + messageId).val() },
                function (str) {
                    $('#message_' + messageId).html(str);
                }
            );
        }
    </script>
}

_EditMessage.cshtml

@if (Model.Edit) {
    <div class="mb-3">
        <label class="form-label">@Model.Message.Type</label>
        <textarea class="form-control" id="[email protected]" rows="@(Model.Message.Message.Count(c => c == '\n') + 1)">@Model.Message.Message</textarea>
    </div>
    <div>
        <button class="btn btn-outline-success" onclick="Save(@Model.Message.Id)">Save</button>
    </div>
}
else {
    var result = Markdown.ToHtml(Model.Message.Message, pipeline);

    <label class="form-label">@Model.Message.Type:</label>
    @Html.Raw(result)
}

_EditMessage.cshtml.cs

[IgnoreAntiforgeryToken]
public class _EditMessageModel : PageModel
{
    public TextMessage Message { get; set; } = null!;
    public bool Edit { get; set; }

    [BindProperty] public string NewMessage { get; set; } = string.Empty;

    public IActionResult OnGet(int messageId) {
        var message = MessageManager.Messages.FirstOrDefault(m => m.Id == messageId);
        if (message is null)
            return NotFound(messageId);

        Message = message;
        Edit = true;

        return Page();
    }

    public IActionResult OnPost(int messageId) {
        var message = MessageManager.Messages.FirstOrDefault(m => m.Id == messageId);
        if (message is null)
            return NotFound(messageId);

        Message = message;
        Message.Message = NewMessage;
        Edit = false;

        return Page();
    }
}
c# razor-pages antiforgerytoken
1个回答
0
投票

我是否为每条消息生成一个令牌,然后以某种方式将其收集起来并与我的 POST 一起发送,或者......?

是的,您需要检索令牌并将其添加到每个

POST
请求中。

根据 learnrazorpages.com 上的页面

如果在请求中省略该值,服务器将返回 400 Bad Request 结果。

这个SO答案提供了一个函数,可以从您的问题中调用

save()
函数。在
post
上,该函数从
input
创建的隐藏
@Html.AntiForgeryToken()
字段中检索防伪令牌。在
@Html.AntiForgeryToken()
页面正文中添加
View.cshtml

为了简单起见,每次调用

save()
函数时都会检索令牌。但是,您可以在 JavaScript 中调用一次
addAntiForgeryToken()
函数,存储其值,然后将令牌值附加到每个后续
post
请求中。

@section Scripts {
    <script>
        function Edit(messageId) {
            $.get(
                "/Messages/_EditMessage?messageId=" + messageId,
                function (str) {
                    $('#message_' + messageId).html(str);
                }
            );
        }
        function Save(messageId, content) {
            let data = { NewMessage: messageId };
            data = addAntiForgeryToken(data);
            console.log(data);
            $.post(
                "/Messages/_EditMessage?messageId=" + messageId,
                data,
                function (str) {
                    $('#message-response').html(str);
                }
            );
        }

        // CSRF (XSRF) security
        // https://stackoverflow.com/questions/73037094/how-to-do-an-ajax-post-with-mvc-antiforgerytoken
        function addAntiForgeryToken(data) {
            //if the object is undefined, create a new one.
            if (!data) {
                data = {};
            }
            //add token
            const tokenInput = $('input[name=__RequestVerificationToken]');
            if (tokenInput.length) {
                data.__RequestVerificationToken = tokenInput.val();
            }
            return data;
        }
    </script>
}
© www.soinside.com 2019 - 2024. All rights reserved.