防伪标记无法解密

81

我有一个表单:

@using (Html.BeginForm(new { ReturnUrl = ViewBag.ReturnUrl })) {
@Html.AntiForgeryToken()
@Html.ValidationSummary()...

并采取行动:

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl, string City)
{
}

偶尔(一周一次),我会遇到以下错误:

防伪标记无法解密。如果此应用程序托管在Web Farm或集群中,请确保所有计算机都运行相同版本的ASP.NET Web Pages,并且配置指定了显式加密和验证密钥。AutoGenerate不能在集群中使用。

我尝试将以下内容添加到webconfig文件中:

<machineKey validationKey="AutoGenerate,IsolateApps"  
    decryptionKey="AutoGenerate,IsolateApps" />

但是错误有时仍会偶尔出现。

我注意到这个错误的出现,例如当一个人从一台电脑来到另一台电脑然后尝试执行操作。

或者有时候由于任何jQuery代码将自动值设置为错误的数据类型,例如将布尔值设置为整数输入字段,请也检查它。


AutoGenerate不会在所有机器上创建相同的密钥。我很困惑你为什么说它不能使用,然后又试图使用它。 - Erik Philips
请明确指定(加密密钥),可以给个示例吗? - user3331122
“AutoGenerate”在应用程序池重新加载时会生成一个新密钥,导致旧的w3wp.exe处理表单时与新的w3wp.exe发生问题,你觉得呢? - sisve
当我打开登录窗口很长时间后,尝试登录时出现了这个错误。只需重新加载此登录窗口并尝试登录即可解决问题。但我不知道如何修复。 - Abdulla Sirajudeen
14个回答

149

我也遇到了这个错误,原因是在同一个表单中防伪标记被应用了两次。 第二个实例来自于一个局部视图,所以不是很明显。


我如何知道这是否发生在我身上,以及我该如何修复它? - John Shedletsky
通过在我的部分视图中注释掉AntiForgery令牌,我停止了收到错误。现在的问题是,如果该部分视图在多个不同的页面上使用,我就不知道何时应用AntiForgery令牌。 - John Shedletsky
2
我也因页面上存在多个防伪标记(在MVC默认模板中,Log Off被包装在表单元素中)而遇到了这个错误,但是我正在进行ajax回发。然后我意识到我调用了$('form').serialize(),于是将其更改为$('#my-form-id').serialize()。 - Quinton Smith
3
只要页面只传递一个令牌到表单中,那么页面有两个令牌是可以的。检查生成的HTML源代码,确保页面上每个表单只有一个包含反伪造令牌的隐藏字段。在生成的页面源代码中搜索 "__RequestVerificationToken"。 - Steve Dowling
1
昨天我检查了页面上的所有表单,没有发现任何问题 - 每个表单都只有一个令牌。今天再次检查时,显示此错误的两个站点实例都正常工作。这是IIS或某些工作进程出了问题吗?代码在过夜期间没有更改。 - Paul F
显示剩余5条评论

35

validationKey="AutoGenerate"

这告诉ASP.NET在每次应用程序启动时生成一个新的加密密钥,用于加密诸如身份验证票据和防伪令牌之类的内容。如果您收到了使用不同密钥(例如,在重新启动之前)加密请求项(例如身份验证cookie)的请求,则可能会出现此异常。

如果您从“AutoGenerate”移开并明确指定加密密钥,则依赖于该密钥正确解密和验证的请求将在每次应用程序重启时正常工作。例如:

<machineKey  
validationKey="21F090935F6E49C2C797F69BBAAD8402ABD2EE0B667A8B44EA7DD4374267A75D7
               AD972A119482D15A4127461DB1DC347C1A63AE5F1CCFAACFF1B72A7F0A281B"           
decryptionKey="ABAA84D7EC4BB56D75D217CECFFB9628809BDB8BF91CFCD64568A145BE59719F"
validation="SHA1"
decryption="AES"
/>

您可以在MSDN页面尽情阅读:如何在ASP.NET中配置MachineKey


请明确指定加密密钥,例如validationKey="sdasdf34234defsdf+-",decryptionKey="sdasdf34234defsdf+-"。这个密钥将适用于所有用户吗? - user3331122
是的,在Webfarm场景中,就是这样验证请求的。希望对你有所帮助。 - Domin8urMind
我相信这正在发生在我身上。我经常部署,即使在高流量时段,似乎只有在应用程序重新启动/部署时才会发生这种情况。 - ganders
1
拥有机器ID将使我们在迁移到新服务器时变得更加复杂。当我们迁移到新服务器时,必须有人有意识地更新web.config文件,是否有其他选项可供尝试? - user2081126

19

只需从链接中为您的框架版本生成<machineKey .../>标签,并将其插入到Web.config中的<system.web><system.web/>中(如果不存在)。

希望这有所帮助。


1
我知道微软提供了生成密钥的代码,但是那个链接让生活变得更加轻松。感谢您提供位置和快速解决方法。如果这是我的问题,我会将其标记为答案 :P - Tony
拥有机器ID将使我们在迁移到新服务器时变得更加复杂。当我们迁移到新服务器时,必须有人有意识地更新web.config文件,是否有其他选项可供尝试? - user2081126

18

如果您因为自己的开发者机器出现这个错误而从Google跳转到这里,请尝试在浏览器中清除cookies。清除浏览器cookies对我有用。


在生产环境中也对我起了作用。 - Wouter Vanherck

8
asp.net Core中,您应该设置数据保护系统。我在Asp.Net Core 2.1或更高版本中进行了测试。
有多种方法可以实现此功能,您可以在配置数据保护替换ASP.NET Core中的ASP.NET machineKey密钥存储提供程序中找到更多信息。
  • first way: Local file (easy implementation)

    startup.cs content:

    public class Startup
    {
       public Startup(IConfiguration configuration, IWebHostEnvironment webHostEnvironment)
       {
           Configuration = configuration;
           WebHostEnvironment = webHostEnvironment;
       }
    
       public IConfiguration Configuration { get; }
       public IWebHostEnvironment WebHostEnvironment { get; }
    
       // This method gets called by the runtime.
       // Use this method to add services to the container.
       public void ConfigureServices(IServiceCollection services)
       {
           // .... Add your services like :
           // services.AddControllersWithViews();
           // services.AddRazorPages();
    
           // ----- finally Add this DataProtection -----
           var keysFolder = Path.Combine(WebHostEnvironment.ContentRootPath, "temp-keys");
           services.AddDataProtection()
               .SetApplicationName("Your_Project_Name")
               .PersistKeysToFileSystem(new DirectoryInfo(keysFolder))
               .SetDefaultKeyLifetime(TimeSpan.FromDays(14));
       }
    }
    
  • second way: save to db

    The Microsoft.AspNetCore.DataProtection.EntityFrameworkCore NuGet package must be added to the project file

    Add MyKeysConnection ConnectionString to your projects ConnectionStrings in appsettings.json > ConnectionStrings > MyKeysConnection.

    Add MyKeysContext class to your project.

    MyKeysContext.cs content:

    public class MyKeysContext : DbContext, IDataProtectionKeyContext
    {
       // A recommended constructor overload when using EF Core 
       // with dependency injection.
       public MyKeysContext(DbContextOptions<MyKeysContext> options) 
           : base(options) { }
    
       // This maps to the table that stores keys.
       public DbSet<DataProtectionKey> DataProtectionKeys { get; set; }
    }
    

    startup.cs content:

    public class Startup
    {
       public Startup(IConfiguration configuration)
       {
           Configuration = configuration;
       }
    
       public IConfiguration Configuration { get; }
    
       // This method gets called by the runtime.
       // Use this method to add services to the container.
       public void ConfigureServices(IServiceCollection services)
       {
           // ----- Add this DataProtection -----
           // Add a DbContext to store your Database Keys
           services.AddDbContext<MyKeysContext>(options =>
               options.UseSqlServer(Configuration.GetConnectionString("MyKeysConnection")));
    
           // using Microsoft.AspNetCore.DataProtection;
           services.AddDataProtection()
               .PersistKeysToDbContext<MyKeysContext>();
    
           // .... Add your services like :
           // services.AddControllersWithViews();
           // services.AddRazorPages();
       }
    }
    

当密钥的生命周期到期时,它们会自动删除吗? - nkalfov

7
如果您使用Kubernetes,并且为应用程序设置了多个pod,那么这很可能会导致请求验证失败,因为生成RequestValidationToken的pod不一定是在POST回应用程序时验证令牌的pod。解决方法应该是配置您的nginx-controller或任何其他ingress资源,并告诉它进行负载均衡,以便每个客户端都使用一个pod进行所有通信。
更新:我通过向我的ingress添加以下注释来修复它:

https://kubernetes.github.io/ingress-nginx/examples/affinity/cookie/

Name    Description Values
nginx.ingress.kubernetes.io/affinity    Sets the affinity type  string (in NGINX only cookie is possible
nginx.ingress.kubernetes.io/session-cookie-name Name of the cookie that will be used    string (default to INGRESSCOOKIE)
nginx.ingress.kubernetes.io/session-cookie-hash Type of hash that will be used in cookie value  sha1/md5/index

3

我在使用.NET Core 2.1时遇到了这个错误。我通过在Startup中添加数据保护服务来解决它:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDataProtection();
    ....
}

3
我在代码的一个区域遇到了这个问题,我有一个视图调用了一个部分视图,但是我返回的不是部分视图,而是整个视图。
我改变了:
return View(index);

return PartialView(index);
在我的控制器中,这样就解决了我的问题。

2

您在视图中调用了多个@Html.AntiForgeryToken()


这就是我想做的:D 我有一个母版页,并且在母版页中有防伪功能,它总是在页面之外,因此也在表单之外。那么页面中的防伪功能呢?我们需要重复很多次,而且它可能不在位于您的母版页内的表单中。 - Hassan Faghihi

1
我找到了一个非常有趣的解决方法,至少在我的情况下有效。我的视图使用ajax在一个div中动态加载带有表单的部分视图,所有这些都在另一个表单中。主表单可以正常提交,其中一个部分视图可以工作,但另一个不能。唯一的区别是那个工作的部分视图最后有一个空的script标签。
    <script type="text/javascript">
    </script> 

我将其移除后,错误确实出现了。我在另一个部分视图中添加了一个空的脚本标签,它居然可以工作!我知道这并不是最干净的方法...但就速度和开销而言...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接