zzh5945

张智豪的天空
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

验证视图MAC失败 Validation of ViewState MAC Failed

Posted on 2008-08-31 09:54  zzh5945  阅读(1180)  评论(0编辑  收藏  举报
验证视图MAC失败 Validation of ViewState MAC Failed

今天在调试Atlas时遇到错误:

验证视图MAC失败。如果此引用程序由网络场或群集承载,请确保<machineKey>配置指定了相同的 validationKey 和验证算法。不能在群集中使用 AutoGenerate

发生错误的环境:

ASP.NET 2.0,使用Atlas的UpdatePanel,在UpdatePanel中动态加载用户控件,以达到动态更新页面的效果。其中有一个用户控件中使用了GridView。当动态切换页面时,出现上述错误。

问题分析:

经过一番搜索,找到以下的文章:

http://aspadvice.com/blogs/joteke/archive/2006/02/02/15011.aspx

http://forums.asp.net/1173230/ShowPost.aspx

分析后找到了问题的根源。首先,文章中提到,如果用GridView,并且指定了DataKeyNames属性,则出于安全的理由(因为DataKeyNames指定的字段代表数据的主键,且该主键值需要保存在视图状态中发送到客户端,用户如果篡改主键值,会导致安全问题),GridView会要求加密视图状态。为此会自动在页面表单</forms>之前添加一个<input type="hidden" name="__VIEWSTATEENCRYPTED" id="__VIEWSTATEENCRYPTED" value="" /> 。

然而,Atlas的UpdatePanel要求放置在<form></form>内部,也就是</form>之前。这就意味着添加的隐藏input控件没有被放置在UpdatePanel内,而是放置在UpdatePanel和</form>之间。

当UpdatePanel更新时,UpdatePanel内部的控件被提交到服务器进行处理(Patrial Rendering),而整个页面并没有被提交。也就是说隐藏的input控件没有随着一起提交。因此服务器并不知道提交的ViewState被加密了,从而导致MAC验证错误。

解决方法:

通过在Web.config里边添加

<pages enableEventValidation="false" viewStateEncryptionMode ="Never" />

可以解决该问题。

ASP.NET 2.0 and "Validation of ViewState Mac failed" exception

If you get this Exception

[HttpException (0x80004005): Validation of viewstate MAC failed. If this application is hosted by a Web Farm or cluster, ensure that <machineKey> configuration specifies the same validationKey and validation algorithm. AutoGenerate cannot be used in a cluster.]

and

  • you know *for sure* that you aren't using a web farm
  • it seems that it appears when using built-in databound controls such as GridView, DetailsView or FormView which utilize DataKeyNames.
  • it appears if you have a large page which loads slowly for any reason

If following preconditions are true and you click a postbacking control/link while the Page hasn't loaded completely, you might get the "Validation of ViewState MAC failed"  exception. In this case be sure to check following post on ASP.NET Forums where this has been discussed quite thoroughly : http://forums.asp.net/1173230/ShowPost.aspx

It appears because GridView using DataKeyNames requires ViewState to be encrypted. And when ViewState is encrypted, Page adds  <input type="hidden" name="__VIEWSTATEENCRYPTED" id="__VIEWSTATEENCRYPTED" value="" /> field just before closing of the <form> tag. But this hidden field might not bet yet rendered to the browser with long-running pages, and if you make a postback before it "gets down", browser initiates postback without this field (in form post collection)

End result is that if this field is omitted on postback, Page doesn't "know" that viewstate is encrypted and causes the prementioned Exception. E.g Page expects to be fully-loaded before you can make a postback. And by the way similar problem is with event validation since __EVENTVALIDATION field is also rendered on the end of the form.

A way to overcome the problem is to set in web.config pages enableEventValidation="false" viewStateEncryptionMode ="Never" />Just note the security implications of these!
在預設狀況ASP.NET會隨機建立manchineKey當作驗證碼,
<configuration>
<system.web >
<machineKey validationKey = "AutoGenerate,IsolateApps"
decryptionKey = "AutoGenerate,IsolateApps" validation = "SHA1"/>
</system.web>
</configuration
若你有兩台Web Server,當另一台要驗證ViewState時,
validationKey或decryptionKey必須是相同的,所以必須自行產生validationKey後,
才能以相同的驗證碼來處理ViewState甚至cookie等。

產生驗證碼的方法如下:

string validationKey = GetKey(30); //20~64均可
string decryptionKey = GetKey(30); //20~64均可

protected string GetKey(int Keylen)
{
byte[] bytes = new byte[Keylen];
new RNGCryptoServiceProvider().GetBytes(bytes);
StringBuilder Builder = new StringBuilder();
for (int i = 0; i < bytes.Length; i++)
{
Builder.Append(string.Format("{0:X2}", bytes[i]));
}
return Builder.ToString();
}

產生後即可在每台Web Server網站的Web.Config加上<machineKey>即可,如下
<configuration>
<system.web>
<machineKey validationKey="3FF1E929BC0534950B0920A7B59FA698BD02DFE8"
decryptionKey="280450BB36319B474C996B506A95AEDF9B51211B1D2B7A77"
decryption="3DES" validation="SHA1"/>
</system.web>
</configuration>