我正在尝试为我的MVC3应用程序实现AntiForgeryToken。设置FormAuthentication cookie后,我遇到了AntiForgeryToken的问题。这是一个简单的例子,它解释了我的问题。
我有家庭控制器,有以下行动方法:
public class HomeController : Controller
{
public ActionResult Logon()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Logon(string userName, string password)
{
FormsAuthentication.SetAuthCookie(userName, false);
return View("About");
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult About(FormCollection form)
{
return View("PageA");
}
}
这是我的登录和关于视图:
Logon.cshtml:
@using (Html.BeginForm("Logon", "Home"))
{
@Html.AntiForgeryToken()
<label> UserName :</label>
<input name = "userName" type="text"/>
<br />
<label> Password :</label>
<input name = "password" type="password"/>
<br />
<br />
<input type="submit" value="LogOn" />
}
About.cshtml
@using (Html.BeginForm("About", "Home"))
{
@Html.AntiForgeryToken()
<p> This is conent of page About</p>
<input name = "moreInfo" type="text"/>
<input type="submit" value="SubmitAbout" />
}
我对“登录”帖子方法没有任何问题。它正在验证antiforgerytoken和渲染About视图。有趣的是,当我在“关于”视图上发帖时,我收到错误“未提供所需的防伪标记或无效”
有人可以指出我在这里做错了吗?
感谢您的帮助。
答案 0 :(得分:5)
我做了一些测试,并确定即使您致电FormsAuthentication.SetAuthCookie(...)
,问题仍然是httpContext.User.Identity.Name
在请求期间仍然为空。
因此,要解决此问题,您需要手动设置当前User
:
FormsAuthentication.SetAuthCookie(email, true);
this.HttpContext.User = new GenericPrincipal(new GenericIdentity(email), null);
这将设置调用User
时使用的正确Html.AntiForgeryToken()
。
请注意,普通PRG模式网站不需要此代码,因为重定向后,将加载正确的User
。
此外,由于您的Logon
方法需要有效的用户名和密码,因此它不太容易受到CSRF攻击,因此您可能不需要在该方法上使用ValidateAntiForgeryToken
。也许这就是AntiForgeryToken依赖于用户名的原因。 CSRF攻击通常只利用已经过身份验证的用户。
答案 1 :(得分:2)
我似乎记得,一旦您登录您的令牌现在与您的用户名不同,我相信会更改此令牌,因此将不再有效。我会试着仔细检查一下,但几乎可以肯定我过去遇到了这个问题。
但是,在上面的代码中,如果使用此模式,则会遇到其他问题。除非出现异常/验证错误并且您正在重新显示页面,否则后期操作通常不会显示视图。通常你会重定向。我看到有人在上面的评论中触及了这一点,他们是正确的。
这并不意味着您不应该使用这些操作,但要注意通过登录进行此操作。此前帖提到使用带令牌的用户名:
Troubleshooting anti-forgery token problems
public void Validate(HttpContextBase context, string salt) {
Debug.Assert(context != null);
string fieldName = AntiForgeryData.GetAntiForgeryTokenName(null);
string cookieName = AntiForgeryData.GetAntiForgeryTokenName(context.Request.ApplicationPath);
HttpCookie cookie = context.Request.Cookies[cookieName];
if (cookie == null || String.IsNullOrEmpty(cookie.Value)) {
// error: cookie token is missing
throw CreateValidationException();
}
AntiForgeryData cookieToken = Serializer.Deserialize(cookie.Value);
string formValue = context.Request.Form[fieldName];
if (String.IsNullOrEmpty(formValue)) {
// error: form token is missing
throw CreateValidationException();
}
AntiForgeryData formToken = Serializer.Deserialize(formValue);
if (!String.Equals(cookieToken.Value, formToken.Value, StringComparison.Ordinal)) {
// error: form token does not match cookie token
throw CreateValidationException();
}
string currentUsername = AntiForgeryData.GetUsername(context.User);
if (!String.Equals(formToken.Username, currentUsername, StringComparison.OrdinalIgnoreCase)) {
// error: form token is not valid for this user
// (don't care about cookie token)
throw CreateValidationException();
}
if (!String.Equals(salt ?? String.Empty, formToken.Salt, StringComparison.Ordinal)) {
// error: custom validation failed
throw CreateValidationException();
}
}
答案 2 :(得分:2)
如果请求中存在具有相同名称的cookie,则AntiForgeryToken Helper不会向响应中添加任何cookie。此外,AntiForgeryToken Helper使用Principal.Identity.Name返回隐藏字段的值。
AntiForgeryData formToken = new AntiForgeryData(cookieToken) {
Salt = salt,
Username = AntiForgeryData.GetUsername(httpContext.User)
};
因此,当您的Login视图使用Html.AntiForgeryToken时,会在响应时设置一个新cookie,并在隐藏字段中设置相同的值。当您的Login视图使用隐藏字段发布此cookie时,不会抛出任何异常,因为请求cookie和隐藏字段值都匹配。但是在About视图的情况下,不会在响应中添加额外的cookie,但由于IIdentty,将为helper返回一个新的隐藏值。因此,当您发布“关于”操作时,将引发异常,因为cookie和隐藏值不匹配。
这可能是AntiForgeryToken实施中的错误。