10

在同一页面的另一个视图中成功登录后,我需要在位于视图中的表单中重新加载 AntiForgeryToken。

我可以通过 jQuery 使用来自结果登录页面的新键在表单输入 @Html.AntiForgeryToken() 键中进行更新吗?

如果是,这是推荐和安全的吗?

我该怎么做?

编辑:

在布局中,我有不同的 PartialViews:

登录部分:

<ul class="menudrt" id="headerLogin">
    @{ Html.RenderAction(MVC.Account.LoginHeader()); }
</ul>

在另一个部分中,可以发送评论:

<div class="comentsform">

    <!-- Comments form -->
    @{ Html.RenderAction(MVC.Comment.Create()); }

</div>

要发送评论,用户必须登录,所以在登录后,评论表单需要更新 AntiForgeryToken 否则我会收到验证错误,因为现在已经登录了。

谢谢

4

2 回答 2

24

出现此问题是因为 AntiForgery 令牌包含当前经过身份验证的用户的用户名。

所以这就是发生的事情:

  1. 匿名用户导航到您的页面
  2. 为评论表单生成了一个防伪令牌,但该令牌包含一个空的用户名(因为此时用户是匿名的)
  3. 您正在使用 AJAX 调用登录
  4. 用户向服务器提交评论表单并且令牌验证失败,因为初始令牌中包含的空用户名与当前已验证的用户名不同。

所以你有几个选项来解决这个问题:

  1. 在第 3 步,不要使用 AJAX 调用。使用标准表单提交登录用户并将他重定向回最初请求的页面。评论表单当然会被重新加载并为其生成正确的防伪令牌。
  2. 登录后刷新防伪令牌

解决方案 1. 的显而易见性并不适合在我的回答中涵盖它。让我们看看如何实施第二种解决方案。

但首先让我们用一个例子重现这个问题:

控制器:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Login()
    {
        FormsAuthentication.SetAuthCookie("john", false);
        return Json(new { success = true });
    }

    [HttpPost]
    [ValidateAntiForgeryToken()]
    public ActionResult Comment()
    {
        return Content("Thanks for commenting");
    }
}

~/Views/Home/Index.cshtml

<div>
    @{ Html.RenderPartial("_Login"); }
</div>

<div id="comment">
    @{ Html.RenderPartial("_Comment"); }
</div>

<script type="text/javascript">
    $('#loginForm').submit(function () {
        $.ajax({
            url: this.action,
            type: this.method,
            data: $(this).serialize(),
            success: function (result) {
                alert('You are now successfully logged in');
            }
        });
        return false;
    });
</script>

~/Views/Home/_Login.cshtml

@using (Html.BeginForm("Login", null, FormMethod.Post, new { id = "loginForm" }))
{
    @Html.AntiForgeryToken()
    <button type="submit">Login</button>
}

~/Views/Home/_Comment.cshtml

@using (Html.BeginForm("Comment", null, FormMethod.Post))
{ 
    @Html.AntiForgeryToken()
    <button type="submit">Comment</button>
}

现在好了,当您导航到 Home/Index 时,将呈现相应的视图,如果您在没有先登录的情况下按下 Comment 按钮,它将起作用。但是如果你登录然后评论它会失败。

因此,我们可以添加另一个控制器操作,该操作将通过简单的Html.AntiForgeryToken调用返回部分视图,以生成新的令牌:

public ActionResult RefreshToken()
{
    return PartialView("_AntiForgeryToken");
}

和相应的部分(~/Views/Home/_AntiForgeryToken.cshtml):

@Html.AntiForgeryToken()

最后一步是通过更新我们的 AJAX 调用来刷新令牌:

<script type="text/javascript">
    $('#loginForm').submit(function () {
        $.ajax({
            url: this.action,
            type: this.method,
            data: $(this).serialize(),
            success: function (result) {
                $.get('@Url.Action("RefreshToken")', function (html) {
                    var tokenValue = $('<div />').html(html).find('input[type="hidden"]').val();
                    $('#comment input[type="hidden"]').val(tokenValue);
                    alert('You are now successfully logged in and can comment');
                });
            }
        });
        return false;
    });
</script>
于 2013-06-07T06:26:34.627 回答
7

您可以通过在他们登录后简单地返回 AntiForgeryToken 来实现这一点。

无需重复使用同一个令牌 2 次。

控制器:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model)
{
  // do something with login
  // return new token as a partial to parse and get value
  return this.PartialView("_AntiForgeryPartial");
}

_AntiForgeryPartial:

@Html.AntiForgeryToken()

您可以使用与此类似的 JS 仅将新的 AntiForgeryToken 值加载到评论表单中。

看法:

$("#LoginForm").submit(function (e) {
    e.preventDefault();

    var $this = $(this);

    $.ajax({
        type: $this.attr("method"),
        url: $this.attr("action"),
        data: $this.serialize(),
        success: function (response) {
            // get the new token from the response html
            var val = $(response).find('input[type="hidden"]').val();
            // set the new token value
            $('.commentsform input[type="hidden"]').val(val);
        }
    });
});

当评论表单执行 POST 时,您应该能够针对新的唯一 AntiForgeryToken 进行验证。

AntiForgeryToken()如果您想了解更多关于如何使用它以及它的用途,Steven Sanderson 有一篇很棒的帖子。

于 2013-06-06T19:14:03.260 回答