Net应用集群负载之MachineKey,0需要考虑的Cookie问题
分类:web前端

在网络场或群集中,或者在某些做了 CDN 加载的虚拟主机中,常常会出现 Cookie 提前过期、验证试图状态 MAC 失败这类的错误。

  当你准备将Web应用程序从ASP.NET 1.1升级到ASP.NET 2.0,你将面对这样一个cookie问题:在ASP.NET 1.1应用程序中客户端保存的所有cookie将失效。

iis负载异常:

有人给的解决方案是:web.config 里加 EnableEventValidation="false" EnableViewStateMac="false" ViewStateEncryptionMode="Never"。

  博客园也遇到了这样的问题,对博客园来说,意味着所有使用cookie的用户都需要重新登录,虽然这不是一个很大的问题,但的确给大家带来了麻烦,如果忘记了密码,将更加麻烦。  

Unable to validate data. at System.Web.Configuration.MachineKeySection.GetDecodedData(Byte[] buf, Byte[] modifier, Int32 start, Int32 length, Int32& dataLength) at System.Web.UI.ObjectStateFormatter.Deserialize(String inputString) HttpApplication.RaiseOnError => EventHandler.Invoke => Global.Application_Error

这个方案也太……既然出错,那咱就不用了吧。怎么能这样呢?出错就不用了?出错得解决问题。

  对于一个非常重视用户满意度的网站来说,应该努力去解决这个问题。博客园希望尽可能减少升级带来的影响,所以这两天我一直在研究这个问题并找到了解决方法。

如果你的Asp.Net程序执行时碰到这种错误:“验证视图状态 MAC 失败。如果此应用程序由网络场或群集承载,请确保 <machineKey> 配置指定了相同的 validationKey 和验证算法。不能在群集中使用 AutoGenerate。” 那么说明你没有让你的应用程序使用统一的machineKey,那么machineKey的作用是什么呢?按照MSDN的标准说法:“对密钥进行配置,以 便将其用于对 Forms 身份验证 Cookie 数据和视图状态数据进行加密和解密,并将其用于对进程外会话状态标识进行验证。”也就是说Asp.Net的很多加密,都是依赖于machineKey里面 的值,例如Forms 身份验证 Cookie、ViewState的加密。默认情况下,Asp.Net的配置是自己动态生成,如果单台服务器当然没问题,但是如果多台服务器负载均 衡,machineKey还采用动态生成的方式,每台服务器上的machinekey值不一致,就导致加密出来的结果也不一致,不能共享验证和 ViewState,所以对于多台服务器负载均衡的情况,一定要在每台站点配置相同的machineKey。

错误原因

  问题的原因是:当程序从ASP.NET 1.1升级到于ASP.NET 2.0后,ASP.NET 2.0使用新的算法与密钥对客户端发送过来的cookie进行解密,这样导致ASP.NET中生成的cookie在ASP.NET 2.0中失效。在ASP.NET 1.1中,使用3DES算法对cookie的内容进行加密,而在ASP.NET 2.0中默认使用Advanced Encrypted Standards (AES)算法进行解密,这是引起问题的原因之一,通过相应的设置可以将ASP.NET 2.0中将cookie加密算法改为3DES,只需在web.config中加上:<machineKey decryption="3DES"/>。但这样做之后问题依然存在,因为解密时除了需要相同的算法,还需要相同的密钥。如果没有在machineKey中指定密钥,ASP.NET 2.0会默认会使用随机生成的密钥,这个随机密钥由System.Web.HttpRuntime.SetAutogenKeys()生成并存储于System.Web.HttpRuntime.s_autogenKeys中,通过反射你可以获取这个值。ASP.NET 1.1的machineKey是在machine.config中进行设置的,默认也是使用随机密钥:
<machineKey validationKey="AutoGenerate,IsolateApps" decryptionKey="AutoGenerate,IsolateApps" validation="SHA1"/>。
问题就出在不同的随机密钥上。如果你在原来的ASP.NET 1.1中指定了密钥,那就不存在这个问题了,但一般在使用Web farm时,才会考虑这一点。所以通常情况都是使用随机密钥。ASP.NET会为不同的应用程序生成不同的随机密钥,这个客户端cookie失效问题会出一在很多情况下,比如:重装系统、将ASP.NET应用程序移至另外一台计算机,将Web应用程序移到不同的虚拟目录中等等。

 

ASP.NET 中有很多涉及到加密的东西,比如 ViewState,比如 FormsAuthenticationTicket,这些东西都是要传送到客户端的,加密才能保障其安全性。

  如何解决这个问题呢?

machineKey生成的算法:

加密就得有个私钥,但这个私钥我们并没有指定啊,那是因为 ASP.NET 自动生成的。

  原理很简单,只要我们知道在ASP.NET 1.1中随机生成的密钥的值,然后在ASP.NET 2.0应用程序的web.config中进行指定就行了,这里的密钥有两个:一个是加密密钥decryptionKey,一个是散列计算密钥validationKey(防止cookie被中途篡改)。假如我们知道密钥分别为:X、Y,那在web.config
进行如下设置就能解决问题:

validationKey = CreateKey(20);

但是如果是在网络场或群集中,或者在某些做了 CDN 加载的虚拟主机中,由于涉及到多台服务器 ASP.NET 就无法为各台机器自动生成相同的私钥,这就造成了这个服务器产生的数据,那台服务器解析不出来。于是就出错了。

<machineKey validationKey="X" decryptionKey="Y" decryption="3DES"/>

decryptionKey = CreateKey(24);

怎么办?

  而难题就在于如何得到ASP.NET 1.1中随机生成的密钥的值。密钥存储在LSA(Windows Local Security Authority)中,但我没找到可以从LSA获取密钥的方法。

     protected string CreateKey(int len)

既然 ASP.NET 在多台服务器上无法自动随机生成相同的私钥,那只有我们自己指定了。

  由于博客园主要是解决登录cookie的问题,而这个cookie是在System.Web.Security.FormsAuthentication. SetAuthCookie(string userName, bool createPersistentCookie)中生成的,所以我就从ASP.NET 1.1的System.Web.Security.FormsAuthentication的源代码下手,发现了System.Web.Configuration.MachineKey,经过进一步对MachineKey的源代码进行研究,在MachineKey的MachineKeyConfig中发现了两个密钥分别存在于s_validationKey与s_oDes这两个私有静态成员中(发现这个费了不少功夫),validationKey的值直接存储于s_validationKey中,而decryptionKey存储于s_oDes.Key中。由于MachineKey是internal class,MachineKeyConfig是私有类型,那两个成员是私有静态成员,无法直接访问。这时,该是.NET中强大的反射功能发挥作用的时候了。通过反射得到这两个值,需要注意的是这两个值的类型是Byte[],通过测试发现直接转换成字符串生成的密钥无效,需要通过反射调用System.Web.Configuration.MachineKey.ByteArrayToHexString(Byte[], Int32) 转换成字符串。

     {

在 web.config 的 configuration/system.web/ 下配置

  今天晚上终于解决了这个问题,好兴奋!中途几次想放弃,但想到在博客园程序升级到ASP.NET 2.0后,会因为这个问题给很多人带来麻烦,虽然只需要重新登录一下就行了,但我还是觉得要解决这个问题,做程序开发不就是尽可能给用户带来方便吗?

            byte[] bytes = new byte[len];

<machineKey validationKey="AutoGenerate|value[,IsolateApps]" decryptionKey="AutoGenerate|value[,IsolateApps]" validation="[SHA1|MD5|3DES]" decryption="[Auto|]" />

  解决了这个问题就为博客园网站升级到ASP.NET 2.0作好了进一步的准备。图片 1

            new RNGCryptoServiceProvider().GetBytes(bytes);

  • decryption 解密算法。可选值:Auto(默认)、AES、3DES。(在 .NET Framework 1.1 及以下版本中,没有此属性)
  • decryptionKey 加解密密钥。这里要自己手动设置为十六进制字符串,长度根据所选密钥设定。
  • validation 验证算法。可选值:AES、MD5、SHA1(默认)、3DES
  • validationKey 验证密钥,用来创建 MAC(消息身份验证代码),以保证视图状态未被更改。这里要自己手动设置为十六进制字符串,长度根据所选密钥设定。

  相关文章:[代码示例]如何在ASP.NET中获取随机生成的cookie加密与验证密钥

              StringBuilder sb = new StringBuilder();

MSDN 上说,.NET Framework 2.0 及以后版本中 validation 的 3DES 用 TripleDES 代替,但我实际使用中发现,在 web.config 中,仍然不能用 TripleDES,仍然是 3DES。

              for(int i = 0; i < bytes.Length; i++)

关于密钥长度

              {   

对于 decryptionKey

                   sb.Append(string.Format("{0:X2}",bytes[i]));

  • 如果 decryption 值为 DES,则 decryptionKey 应为 16 个十六进制字符长,即 8 个字节。(这个现在已经不用了)
  • 如果 decryption 值为 AES、3DES,则 decryptionKey 应为 48 个十六进制字符长,即 24 个字节。

              }

对于 validationKey

              return sb.ToString();

  • 不论 validation 为何值,validationKey 应为 40 到 128 个十六进制字符长,即 20 到 64 个字节。建议选用 SHA1(MD5 虽然性能好些,但安全性要比 SHA1 差些)。

     }

关于这些加密,请参见千一网络的连载文章 C# 加密。

附参考的matchineKey配置:

更多关于 machineKey,请参见 。

<?xml version="1.0"?>

要了解创建 machineKey 的代码及使用该工具,请参见创建生成 MachineKey 的代码。

<configuration>

  <system.web>

    <machineKey validationKey="3FF1E929BC0534950B0920A7B59FA698BD02DFE8" decryptionKey="280450BB36319B474C996B506A95AEDF9B51211B1D2B7A77" decryption="3DES" validation="SHA1"/>

     </system.web>

</configuration>


 

加密MachineKey

ASP.NET中设置MachineKey可以很轻松的实现SSO,可以在所有ASP.NET站点中添加如下配置:
<machineKey validationKey="XXXXXX" decryptionKey="XXX" validation="SHA1" />
validationKey可以为视图状态、身份验证Cookie、Session等重要的信息添加杂乱信息以防止重要信息被篡改。
为了防止validationKey和decryptionKey以明文的方式进行显示,可以使用ProtectSection方法对machineKey配置节进行加密。
1、在Web.config中添加原始的配置,如:
<machineKey validationKey="XXXXXX" decryptionKey="XXX" validation="SHA1" />
2、通过程序对system.web/machineKey节进行加密和解密
加密方式如下:

图片 2            Configuration config = WebConfigurationManager.OpenWebConfiguration("/");
图片 3            ConfigurationSection machineKeySection = config.GetSection("system.web/machineKey");
图片 4            machineKeySection.SectionInformation.ProtectSection("RSAProtectedConfigurationProvider");
图片 5            machineKeySection.SectionInformation.ForceSave = true;
图片 6            config.Save();

解密方式如下:

图片 7            Configuration config = WebConfigurationManager.OpenWebConfiguration("/");
图片 8            ConfigurationSection machineKeySection = config.GetSection("system.web/machineKey");
图片 9            machineKeySection.SectionInformation.UnprotectSection();
图片 10            machineKeySection.SectionInformation.ForceSave = true;

3、通过程序加密就会得到类拟:

图片 11        <machineKey configProtectionProvider="RsaProtectedConfigurationProvider">
图片 12            <EncryptedData Type=""
图片 13                xmlns=";
图片 14                <EncryptionMethod Algorithm="" />
图片 15                图片 16图片 17图片 18图片 19图片 20图片 21图片 22
图片 23            </EncryptedData>
图片 24        </machineKey>

这样的配置,你只要把这段配置复制到各个需要SSO的站点的Web.config就可以了,系统在运行过程中会自动进行解密
如果想变回原来的明文显示可以执行解密的相反过程就行
注意:其中的OpenWebConfiguration("/");表示打开站点根目录下的web.config,其它情况可参考如下:
Using the Management API

 

生成MachineKey:

 

本文由10bet手机官网发布于web前端,转载请注明出处:Net应用集群负载之MachineKey,0需要考虑的Cookie问题

上一篇:宝宝夏日你的福利来啦,每日小段 下一篇:中样式表只有前半部分起作用的原因,关于编码问题【10bet手机官网】
猜你喜欢
热门排行
精彩图文