J'implémente la fonctionnalité de réinitialisation de mot de passe sur mon site en utilisant la classe UserManager
intégrée fournie avec ASP.NET 5.
Tout fonctionne bien dans mon environnement de développement. Cependant, une fois que je l'ai essayé sur le site de production qui s'exécute en tant que site Web Azure, j'obtiens l'exception suivante:
System.Security.Cryptography.CryptographicException : l'opération de protection des données a échoué. Cela peut être dû au fait que le profil utilisateur n'a pas été chargé pour le contexte utilisateur du thread actuel, ce qui peut être le cas lorsque le thread emprunte l'identité.
Voici comment j'ai configuré l'instance UserManager
:
var provider = new Microsoft.Owin.Security.DataProtection.DpapiDataProtectionProvider(SiteConfig.SiteName);
UserManager.UserTokenProvider = new Microsoft.AspNet.Identity.Owin.DataProtectorTokenProvider<User>(provider.Create(ResetPasswordPurpose));
Ensuite, je génère ainsi le token (à envoyer à l'utilisateur dans un mail afin qu'il puisse vérifier qu'il souhaite bien réinitialiser son mot de passe):
string token = UserManager.GeneratePasswordResetToken(user.Id);
Malheureusement, lorsque cela fonctionne sur Azure, je reçois l'exception ci-dessus.
J'ai cherché sur Google et trouvé ceci solution possible . Cependant, cela n'a pas fonctionné du tout et je reçois toujours la même exception.
Selon le lien, cela a quelque chose à voir avec les jetons de session qui ne fonctionnent pas sur une batterie de serveurs Web comme Azure.
Le DpapiDataProtectionProvider utilise DPAPI qui ne fonctionnera pas correctement dans une ferme Web/un environnement cloud, car les données chiffrées ne peuvent être déchiffrées que par la machine qui les a chiffrées. Ce dont vous avez besoin, c'est d'un moyen de chiffrer les données de manière à ce qu'elles puissent être déchiffrées par n'importe quelle machine de votre environnement. Malheureusement, ASP.NET Identity 2.0 n'inclut aucune autre implémentation d'IProtectionProvider autre que DpapiDataProtectionProvider. Cependant, il n'est pas trop difficile de rouler le vôtre.
Une option consiste à utiliser la classe MachineKey comme suit:
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);
}
}
Pour utiliser cette option, vous devez suivre quelques étapes.
Étape 1
Modifiez votre code pour utiliser le MachineKeyProtectionProvider.
using Microsoft.AspNet.Identity.Owin;
// ...
var provider = new MachineKeyProtectionProvider();
UserManager.UserTokenProvider = new DataProtectorTokenProvider<User>(
provider.Create("ResetPasswordPurpose"));
Étape 2
Synchronize the MachineKey valeur sur toutes les machines de votre batterie de serveurs Web/environnement cloud. Cela semble effrayant, mais c'est la même étape que nous avons effectuée un nombre incalculable de fois auparavant pour que la validation ViewState fonctionne correctement dans une batterie de serveurs Web (elle utilise également DPAPI).
Pensez à utiliser IAppBuilder.GetDataProtectionProvider()
au lieu de déclarer un nouveau DpapiDataProtectionProvider
.
Comme pour vous, j'avais introduit ce problème en configurant mon UserManager comme ceci, à partir d'un exemple de code que j'ai trouvé:
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),
};
}
}
Le problème CodePlex lié à ci-dessus fait en fait référence à un article de blog qui a été mis à jour avec une solution plus simple au problème. Il recommande d'enregistrer une référence statique au IDataProtector
...
public partial class Startup
{
internal static IDataProtectionProvider DataProtectionProvider { get; private set; }
public void ConfigureAuth(IAppBuilder app)
{
DataProtectionProvider = app.GetDataProtectionProvider();
// other stuff.
}
}
... puis le référencer à partir du 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
}
}
La réponse de johnso fournit également un bon exemple de la façon de câbler cela à l'aide d'Autofac.
J'ai eu les mêmes problèmes, sauf que j'hébergeais sur Amazon ec2.
J'ai pu le résoudre en accédant au pool d'applications dans IIS et (sous les paramètres avancés après un clic droit) définissant le modèle de processus - charger le profil utilisateur = true.
J'avais le même problème (Owin.Security.DataProtection.DpapiDataProtectionProvider
échec lors de l'exécution sur Azure), et Staley a raison, vous ne pouvez pas utiliser DpapiDataProtectionProvider
.
Si vous utilisez OWIN Startup Classes vous pouvez éviter de rouler votre propre IDataProtectionProvider
, utilisez plutôt la méthode GetDataProtectionProvider
de IAppBuilder
.
Par exemple, avec Autofac:
internal static IDataProtectionProvider DataProtectionProvider;
public void ConfigureAuth(IAppBuilder app)
{
// ...
DataProtectionProvider = app.GetDataProtectionProvider();
builder.Register<IDataProtectionProvider>(c => DataProtectionProvider)
.InstancePerLifetimeScope();
// ...
}