MachineKeyDataProtector - 通过后台作业

时间:2016-03-01 22:38:09

标签: asp.net-mvc encryption hangfire

我一直把头发拉过来。无论何时通过我的Windows服务(后台任务)发送用户注册电子邮件,我都会收到“无效链接”。

我的设置

我在开发服务器上使用Hangfire作为Windows服务。这是发生问题的GenerateEmailConfirmationToken调用的地方。它位于ASP.NET管道之外的完全不同的上下文中。所以我设置了machineKey值以与MVC应用程序的web.config中的值相对应:

在Windows服务控制台项目的app.config中,转换为MyApp.exe.config,我有一个machineKey元素

在MVC 5项目中 - 我有一个与MyApp.exe.config machineKey元素匹配的machineKey元素。

我已经确认这两者中的机器密钥元素数据相同。

问题

当我使用ASP.NET MVC上下文和管道(IE而不通过Hangfire后台作业处理)生成用户时,链接正常工作

当我使用后台作业处理器时,我总是收到无效链接。我在这里完全没有想法。

为什么会这样?是因为令牌是在不同的线程中生成的吗?我该如何解决这个问题?

各种项目的相关代码

IoC Bootstrapping

由两个应用程序(Windows服务和MVC Web App)调用

container.Register<IUserTokenProvider<AppUser, int>>(() => DataProtector.TokenProvider, defaultAppLifeStyle);

DataProtector.cs

public class DataProtector
    {
        public static IDataProtectionProvider DataProtectionProvider { get; set; }
        public static DataProtectorTokenProvider<AppUser, int> TokenProvider { get; set; } 

        static DataProtector()
        {
            DataProtectionProvider = new MachineKeyProtectionProvider();
            TokenProvider = new DataProtectorTokenProvider<AppUser, int>(DataProtectionProvider.Create("Confirmation", "ResetPassword"));
        }
    }

我尝试过的事情

使用DpapiDataProtectionProvider

来自Generating reset password token does not work in Azure Website

的自定义MachineKeyProtectionProvider

MachineKeyProtectionProvider.cs代码与上面的关联帖子完全相同。

我也尝试过“YourMom”和“AllYourTokensAreBelongToMe”之类的其他目的无济于事。单一目的,多重目的 - 无所谓 - 无效。

我也在两个地方生成的代码(控制器和后台作业)上调用HttpUtility.UrlEncode(code)

解决方案

igor做对了,除了它不是代码问题。这是因为一个流氓服务人员接受了这个有不同机器密钥的工作。我一直在盯着这个问题,以至于我没有看到第二个服务正在运行。

1 个答案:

答案 0 :(得分:1)

据我了解您的问题,有两个可能发生故障的地方。

1。的machineKey

MachineKey本身可能不会在您的2个应用程序之间产生一致的值。如果machineKey文件中的.config在两个应用程序中都不相同,则会发生这种情况(我确实读过您检查过它但是一个简单的类型-o,添加了空格,添加到错误中父元素等可能导致这种行为。)。这可以很容易地测试,以排除它作为一个失败点。此外,行为可能会有所不同,具体取决于引用的.net框架MachineKey.Protect

  

即使MachineKeySection.CompatibilityMode属性未设置为Framework45选项,此方法也需要MachineKeyCompatibilityMode.Framework45选项所需的配置设置。

我创建了一个随机密钥对进行测试并使用此密钥,我在代码中生成了一个我分配给变量validValue的测试值。如果将以下部分复制/粘贴到web.config和app.config中,则该键值的Unprotect将起作用。

web.config / app.config

<system.web>
  <httpRuntime targetFramework="4.6.1"/>
    <machineKey decryption="AES" decryptionKey="9ADCFD68D2089D79A941F9B8D06170E4F6C96E9CE996449C931F7976EF3DD209"  validation="HMACSHA256" validationKey="98D92CC1E5688DB544A1A5EF98474F3758C6819A93CC97E8684FFC7ED163C445852628E36465DB4E93BB1F8E12D69D0A99ED55639938B259D0216BD2DF4F9E73" />
</system.web>

服务申请测试

class Program
{
    static void Main(string[] args)
    {
        // should evaluate to SomeTestString
        const string validValue = "03AD03E75A76CF13FDDA57425E9D362BA0FF852C4A052FD94F641B73CEBD3AC8B2F253BB45550379E44A4938371264BFA590F9E68E59DB57A9A4EB5B8B1CCC59";
        var unprotected2 = MachineWrapper.Unprotect(validValue);
    }
}

Mvc控制器(或Web Api控制器)测试

public class WebTestController : Controller
{
    // GET: WebTest
    public ActionResult Index()
    {
        // should evaluate to SomeTestString
        const string validValue = "03AD03E75A76CF13FDDA57425E9D362BA0FF852C4A052FD94F641B73CEBD3AC8B2F253BB45550379E44A4938371264BFA590F9E68E59DB57A9A4EB5B8B1CCC59";
        var unprotected2 = MachineWrapper.Unprotect(validValue);

        return View(unprotected2);
    }
}

通用代码

using System;
using System.Linq;
using System.Text;
using System.Web.Security;

namespace Common
{
    public class MachineWrapper
    {
        public static string Protect()
        {
            var testData = "SomeTestString";
            return BytesToString(MachineKey.Protect(System.Text.Encoding.UTF8.GetBytes(testData), "PasswordSafe"));
        }

        public static string Unprotect(string data)
        {
            var bytes = StringToBytes(data);
            var result = MachineKey.Unprotect(bytes, "PasswordSafe");
            return System.Text.Encoding.UTF8.GetString(result);
        }

        public static byte[] StringToBytes(string hex)
        {
            return Enumerable.Range(0, hex.Length)
                .Where(x => x % 2 == 0)
                .Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
                .ToArray();
        }
        public static string BytesToString(byte[] bytes)
        {
            var hex = new StringBuilder(bytes.Length * 2);
            foreach (byte b in bytes)
                hex.AppendFormat("{0:x2}", b);
            return hex.ToString().ToUpper();
        }
    }
}

如果同时传递了Console,Web应用程序将获得相同的值,而不会抛出CryptographicException消息Error occurred during a cryptographic operation。如果您想使用自己的密钥进行测试,只需从公共MachineWrapper类运行Protect并记录该值并重新执行这两个应用程序。

2。 UserManager使用错误类型

我将从上一节开始,但另一个失败点是Microsoft.AspNet.Identity.UserManager未使用您的自定义计算机密钥提供程序。所以这里有一些问题/行动项目可以帮助你找出发生这种情况的原因:

  1. 是{I}框架的container.Register还是您正在使用其他框架?
  2. 您确定您的Di框架还在两者服务应用程序以及Web应用程序中的Microsoft.AspNet.Identity.UserManager中注入了该实例吗?
  3. public byte[] Protect课程的MachineKeyDataProtector中设置了一个断点,看看是否在两者服务应用程序以及Web应用程序中调用了此点?
  4. 从我到目前为止看到的示例(包括您使用自定义MachineKey解决方案发布的示例),您需要在应用程序启动期间手动引导类型,但我再次尝试挂钩到Identity框架以替换此组件使用DI。

    如果查看创建新MVC应用程序时提供的默认Visual Studio模板代码,则代码文件 App_Start \ IdentityConfig.cs 将成为添加此新提供程序的位置。

    方法:

    public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
    

    替换

    var dataProtectionProvider = options.DataProtectionProvider;
    if (dataProtectionProvider != null)
    {
        manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
    }
    

    有了这个

    var provider = new MachineKeyProtectionProvider();
    manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(provider.Create("ResetPasswordPurpose"));
    

    如果你没有使用配置它的公共库,那么必须为两个应用程序配置