我一直把头发拉过来。无论何时通过我的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做对了,除了它不是代码问题。这是因为一个流氓服务人员接受了这个有不同机器密钥的工作。我一直在盯着这个问题,以至于我没有看到第二个服务正在运行。
答案 0 :(得分:1)
据我了解您的问题,有两个可能发生故障的地方。
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并记录该值并重新执行这两个应用程序。
我将从上一节开始,但另一个失败点是Microsoft.AspNet.Identity.UserManager
未使用您的自定义计算机密钥提供程序。所以这里有一些问题/行动项目可以帮助你找出发生这种情况的原因:
container.Register
还是您正在使用其他框架?Microsoft.AspNet.Identity.UserManager
中注入了该实例吗? public byte[] Protect
课程的MachineKeyDataProtector
中设置了一个断点,看看是否在两者服务应用程序以及Web应用程序中调用了此点? 从我到目前为止看到的示例(包括您使用自定义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"));
如果你没有使用配置它的公共库,那么必须为两个应用程序配置。