可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I am trying to implement AntiForgeryToken for my MVC3 Application. I am having a problem with AntiForgeryToken after setting FormAuthentication cookie. Here is a simple example which explains my problem.
I have home controller with following action methods:
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");
}
}
And here is my Logon and About views:
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" />
}
I have no problem on "Logon" post method. It is validating the antiforgerytoken and rendering About view. Interestingly, when I do post on "About" view I am getting error "A required anti-forgery token was not supplied or was invalid"
could some one point out what I am doing wrong here?
Appreciate your help.
回答1:
I did some tests, and determined that even after you call FormsAuthentication.SetAuthCookie(...)
, the problem is that httpContext.User.Identity.Name
will still be empty for the duration of the request.
Therefore, to solve this issue, you need to manually set the current User
as so:
FormsAuthentication.SetAuthCookie(email, true);
this.HttpContext.User = new GenericPrincipal(new GenericIdentity(email), null);
This will set the correct User
that is used when Html.AntiForgeryToken()
is called.
Please note that this code isn't necessary for normal PRG-pattern websites, because after the redirect, the correct User
will be loaded.
Also, since your Logon
method requires a valid user name and password, it isn't really susceptible to CSRF attacks, so you probably don't need to use ValidateAntiForgeryToken
on that method. Maybe that's why the AntiForgeryToken is dependent on the user name. CSRF attacks usually only exploit already-authenticated users.
回答2:
I seem to recall once you login your token is now different as your username I believe changes this token, hence would no longer be valid. I'll try to double check this but almost certain I ran into this in the past.
However in your code above you will run up against other issues if you use this pattern. Post actions aren't generally meant to display a view unless there has been an exception/validation error and you are redisplaying the page. Generally you would redirect. I see someone touched upon this in a comment above and they are correct.
This doesn't mean you shouldn't use those actions though but beware of crossing this over a login. This prior post alludes to the use of username with tokens:
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();
}
}
回答3:
The AntiForgeryToken Helper does not add any cookie to response, if a cookie with same name exist in the request. Also the AntiForgeryToken Helper uses Principal.Identity.Name to return a value for hidden field.
AntiForgeryData formToken = new AntiForgeryData(cookieToken) {
Salt = salt,
Username = AntiForgeryData.GetUsername(httpContext.User)
};
So when your Login view uses Html.AntiForgeryToken, a new cookie is set on response and a hidden field with same value. When your Login view post this cookie with hidden field, no exception will be thrown because both request cookie and hidden field value matches. But in the case of About view, no additional cookie will be added to response, but due to IIdentty, a new hidden value will be return for helper. So when you post About action, an exception will raise because cookie and hidden value does not match.
This may be a bug in AntiForgeryToken implementation.