Генерирование reset токена паролей не работает на Azure Website

Я реализую функциональность пароля reset на моем сайте, используя встроенный класс UserManager, который поставляется с ASP.NET 5.

Все работает отлично в моей среде dev. Однако, как только я попробую его на рабочем сайте, который работает как сайт Azure, я получаю следующее исключение:

System.Security.Cryptography.CryptographicException: The data protection operation was unsuccessful. This may have been caused by not having the user profile loaded for the current thread user context, which may be the case when the thread is impersonating.

Вот как я настраиваю экземпляр UserManager:

var provider = new Microsoft.Owin.Security.DataProtection.DpapiDataProtectionProvider(SiteConfig.SiteName);
UserManager.UserTokenProvider = new Microsoft.AspNet.Identity.Owin.DataProtectorTokenProvider<User>(provider.Create(ResetPasswordPurpose));

Затем я генерирую маркер таким образом (для отправки пользователю по электронной почте, чтобы они могли убедиться, что они действительно хотят reset их пароль):

string token = UserManager.GeneratePasswordResetToken(user.Id);

К сожалению, когда это работает на Azure, я получаю исключение выше.

Я искал Google и нашел это возможное решение. Однако это не сработало, и я все равно получаю то же исключение.

В соответствии со ссылкой, это что-то связано с токенами сеанса, которые не работают на веб-ферме, такой как Azure.

Ответ 1

DpapiDataProtectionProvider использует DPAPI, который не будет работать должным образом в среде веб-фермы/облака, поскольку зашифрованные данные могут быть дешифрованы только машиной, которая его зашифровала. Вам нужен способ шифрования данных, чтобы он мог быть расшифрован любой машиной в вашей среде. К сожалению, ASP.NET Identity 2.0 не включает другую реализацию IProtectionProvider, кроме DpapiDataProtectionProvider. Однако, это не слишком сложно катить.

Один из вариантов - использовать класс MachineKey следующим образом:

public class MachineKeyProtectionProvider : IDataProtectionProvider
{
    public IDataProtector Create(params string[] purposes)
    {
        return new MachineKeyDataProtector(purposes);
    }
}

public class MachineKeyDataProtector : IDataProtector
{
    private readonly string[] _purposes;

    public MachineKeyDataProtector(string[] purposes)
    {
        _purposes = purposes;
    }

    public byte[] Protect(byte[] userData)
    {
        return MachineKey.Protect(userData, _purposes);
    }

    public byte[] Unprotect(byte[] protectedData)
    {
        return MachineKey.Unprotect(protectedData, _purposes);
    }
}

Чтобы использовать этот параметр, вам нужно выполнить несколько шагов.

Шаг 1

Измените свой код, чтобы использовать MachineKeyProtectionProvider.

using Microsoft.AspNet.Identity.Owin;
// ...

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

Шаг 2

Синхронизировать значение MachineKey на всех машинах веб-фермы/облачной среды. Это звучит страшно, но это тот же самый шаг, который мы выполняли бесчисленное количество раз, чтобы получить правильную проверку ViewState в веб-ферме (он также использует DPAPI).

Ответ 2

Рассмотрите возможность использования IAppBuilder.GetDataProtectionProvider() вместо объявления нового DpapiDataProtectionProvider.

Подобно вам, я ввел эту проблему, настроив свой UserManager, как это, из найденного кода:

public class UserManager : UserManager<ApplicationUser>
{
    public UserManager() : base(new UserStore<ApplicationUser>(new MyDbContext()))
    {
        // this does not work on azure!!!
        var provider = new Microsoft.Owin.Security.DataProtection.DpapiDataProtectionProvider("ASP.NET IDENTITY");
        this.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(provider.Create("EmailConfirmation"))
        {
            TokenLifespan = TimeSpan.FromHours(24),
        };
    }
}

Проблема CodePlex, связанная с выше, фактически ссылается на сообщение в блоге, которое было обновлено с более простым решением проблемы. Он рекомендует сохранять статическую ссылку на IDataProtector...

public partial class Startup
{
    internal static IDataProtectionProvider DataProtectionProvider { get; private set; }

    public void ConfigureAuth(IAppBuilder app)
    {
        DataProtectionProvider = app.GetDataProtectionProvider();
        // other stuff.
    }
}

... и затем ссылаясь на него из UserManager

public class UserManager : UserManager<ApplicationUser>
{
    public UserManager() : base(new UserStore<ApplicationUser>(new MyDbContext()))
    {
        var dataProtectionProvider = Startup.DataProtectionProvider;
        this.UserTokenProvider = 
                new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));

        // do other configuration
    }
}

Ответ от johnso также служит хорошим примером того, как подключить это с помощью Autofac.

Ответ 3

У меня были те же проблемы, кроме того, что я принимал на amazon ec2. я смог его решить, перейдя в пул приложений в iis и (под дополнительными настройками после щелчка правой кнопкой мыши), установив модель процесса - загрузите профиль пользователя = true.

Ответ 4

У меня была такая же проблема (Owin.Security.DataProtection.DpapiDataProtectionProvider сбой при запуске на Azure), а Staley верен, вы не можете использовать DpapiDataProtectionProvider.

Если вы используете OWIN Startup Classes, вы можете не использовать собственный IDataProtectionProvider, вместо этого используйте метод GetDataProtectionProvider IAppBuilder.

Например, с Autofac:

internal static IDataProtectionProvider DataProtectionProvider;    

public void ConfigureAuth(IAppBuilder app)
{
    // ...

    DataProtectionProvider = app.GetDataProtectionProvider();

    builder.Register<IDataProtectionProvider>(c => DataProtectionProvider)
        .InstancePerLifetimeScope();

    // ...
}