MVC - 防伪令牌错误

时间:2017-07-24 15:02:36

标签: c# asp.net-mvc

我已经参加了我的第一个MVC和C#项目,所以我很感激任何指导。

我创建了一项新功能,可以检查用户在登录时是否接受过安全培训。如果用户没有,则会将用户定向到他们只是同意/不同意的培训页面规则。如果用户同意,则登录完成。如果用户不同意,他/她将被注销。

我遇到的问题是,当我在训练视图中选择同意/不同意按钮时,我得到以下error 它应该将我引导到主页或注销用户。

控制器

 public ActionResult UserSecurityTraining(int ID, string returnUrl)
    {
        // check if user already has taken training (e.g., is UserInfoID in UserSecurityTrainings table)
        var accountUser = db.UserSecurityTraining.Where(x => x.UserInfoID == ID).Count();
        // If user ID is not in UserSecurityTraining table...
        if (accountUser == 0)
    {
        // prompt security training for user
        return View("UserSecurityTraining");
    }
    // If user in UserSecurityTraining table...
    if (accountUser > 0)
    {
        return RedirectToLocal(returnUrl);
    }
    return View();
}

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> UserSecurityTrainingConfirm(FormCollection form, UserSecurityTraining model)
{
    if (ModelState.IsValid)
    {
        if (form["accept"] != null)
        {
            try
            {
                // if success button selected
                //UserSecurityTraining user = db.UserSecurityTraining.Find(); //Create model object
                //var user = new UserSecurityTraining { ID = 1, UserInfoID = 1, CreatedDt = 1 };

                logger.Info("User has successfully completed training" + model.UserInfoID);
                model.CreatedDt = DateTime.Now;
                db.SaveChanges();
                //return RedirectToAction("ChangePassword", "Manage");
            }
            catch (Exception e)
            {
                throw e;
            }
            return View("SecurityTrainingSuccess");
        }
        if(form["reject"] != null)
        {
            return RedirectToAction("Logoff", "Account");
        }
    }
    return View("UserSecurityTraining");
}

查看

@model ECHO.Models.UserSecurityTraining
@{
    ViewBag.Title = "Security Training";
    Layout = "~/Views/Shared/_LayoutNoSidebar.cshtml";
}

<!--<script src="~/Scripts/RequestAccess.js"></script>-->
<div class="container body-content">
    <h2>@ViewBag.Title</h2>
    <div class="row">
        <div class="col-md-8">
            @using (Html.BeginForm("UserSecurityTrainingConfirm", "Account", FormMethod.Post, new { role = "form" }))
            {
                <fieldset>
                    @Html.AntiForgeryToken()
                    Please view the following security training slides:<br><br>
                    [INSERT LINK TO SLIDES]<br><br>
                    Do you attest that you viewed, understood, and promise to follow the guidelines outlined in the security training?<br><br>
                    <input type="submit" id="accept" class="btn btn-default" value="Accept" />
                    <input type="submit" id="reject" class="btn btn-default" value="Reject" />

                </fieldset>
            }                
        </div><!--end col-md-8-->
    </div><!--end row-->
</div><!-- end container -->

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

1 个答案:

答案 0 :(得分:1)

我认为您没有提供足够的代码来正确诊断此特定错误。通常,此防伪例外是由于身份验证状态的更改。当您在页面上调用@Html.AntiForgeryToken()时,在使用令牌的响应中也会设置cookie。重要的是,如果用户已通过身份验证,则使用该用户的身份来撰写该令牌。然后,如果该用户的身份验证状态在设置cookie和将表单发布到验证该令牌的操作之间发生更改,则该令牌将不再匹配。这可以是用户在cookie设置后进行身份验证,也可以在设置cookie后注销。换句话说,如果用户在页面加载时是匿名的,但在提交表单之前登录,则它仍然会失败。

同样,我没有看到任何明显会引起这种情况的代码,但我们也没有全面了解用户如何登录到第一个视图中的地方。

尽管如此,有一些非常明显的错误可能会或可能不会导致这个特定的问题,但肯定会在某个时候引起问题。首先,您的按钮没有name属性。根据您的操作代码,您似乎认为id属性将出现在FormCollection中,但事实并非如此。您需要分别将name="accept"name="reject"添加到按钮中,以便当前代码正常运行。

其次,在用户成功接受时,您应该重定向到加载SecurityTrainingSuccess视图的操作,而不是直接返回该视图。这部分PRG(Post-Redirect-Get)模式确保不重播提交。任何和所有后期操作都应该在成功时重定向。

第三,至少开箱即用,LogOff将是一个后期操作,这意味着你无法重定向到它。重定向总是通过GET。从技术上讲,你可以使LogOff响应GET以及代替POST,但这是一种反模式。应始终通过POST(或更合适的动词,如PUT,DELETE等)处理原子动作,但永远不要GET。

最后,虽然很小,但一般来说,不建议使用FormCollection。对于这样一个简单的表单,你可以将你的帖子绑定为params:

public ActionResult UserSecurityTrainingConfirm(string accept, string reject, ...)

然而,引入单个布尔值可能更合乎逻辑且更加万无一失:

public ActionResult UserSecurityTrainingConfirm(bool accepted, ...)

然后,您的按钮可能只是:

    <button type="submit" name="accepted" value="true" class="btn btn-default">Accept</button>
    <button type="submit" name="accepted" value="false" class="btn btn-default">Reject</button>

这基本上使它们像无线电一样。点击的那个会提交其值,因此accepted参数将相应地为true或false。另外,请注意我将您切换为真正的button元素。使用input按钮是一种不好的做法,特别是当你真正需要它来提交一个值时,因为值和显示本身是绑在一起的。使用button元素,您可以发布任何您想要的内容,并且仍然可以使用您想要的任何文本进行标记。