【问题标题】:ASP.NET Identity 2.0 Invalid Token RandomlyASP.NET Identity 2.0 随机无效令牌
【发布时间】:2015-05-07 14:16:11
【问题描述】:

有时用户在点击他们的电子邮件确认链接时会收到无效令牌。我不知道为什么,这纯粹是随机的。

这是创建用户的代码:

IdentityResult result = manager.Create(user, "Password134567");
if (result.Succeeded)
{
    var provider = new DpapiDataProtectionProvider("WebApp2015");
    UserManager<User> userManager = new UserManager<User>(new UserStore<User>());
    userManager.UserTokenProvider = new DataProtectorTokenProvider<User>(provider.Create(user.Id));
    manager.UserTokenProvider = new DataProtectorTokenProvider<User>(provider.Create("ConfirmUser"));

    var emailInfo = new Email();

    string code = HttpUtility.UrlEncode(Context.GetOwinContext().GetUserManager<ApplicationUserManager>().GenerateEmailConfirmationToken(user.Id));
    string callbackUrl = IdentityHelper.GetUserConfirmationRedirectUrl(code, user.Id, Request);

    if (email.IndexOf("@") != -1)
    {
        if (assignedId == 0)
        {
            lblError.Text = "There was an error adding this user";
            return;
        }
        string emailcontent = emailInfo.GetActivationEmailContent(assignedId, callbackUrl, userRole);
        string subject = emailInfo.Subject;
        if (string.IsNullOrEmpty(subject))
        {
            subject = "Your Membership";
        }
        Context.GetOwinContext()
               .GetUserManager<ApplicationUserManager>()
               .SendEmail(user.Id, subject, emailcontent);

        if (user.EmailConfirmed)
        {
            IdentityModels.IdentityHelper.SignIn(manager, user, isPersistent: false);
            IdentityHelper.RedirectToReturnUrl(Request.QueryString["ReturnUrl"], Response);
        }
        else
        {
            ErrorMessage.ForeColor = Color.Green;
            ErrorMessage.Text = "An email has been sent to the user, once they verify their email they are ready to login.";
        }
    }
    else
    {
        ErrorMessage.ForeColor = System.Drawing.Color.Green;
        ErrorMessage.Text = "User has been created.";
    }

    var ra = new RoleActions();
    ra.AddUserToRoll(txtEmail.Text, txtEmail.Text, userRole);
}
else
{
    ErrorMessage.Text = result.Errors.FirstOrDefault();
}

这是给出“无效令牌”错误的确认页面

protected void Page_Load(object sender, EventArgs e)
{
    var code = IdentityHelper.GetCodeFromRequest(Request);
    var userId = IdentityHelper.GetUserIdFromRequest(Request);
    if (code != null && userId != null)
    {
        var manager = Context.GetOwinContext()
                             .GetUserManager<ApplicationUserManager>();
        var confirmId = manager.FindById(userId);
        if (confirmId != null)
        {
            var result = manager.ConfirmEmail(userId, HttpUtility.UrlDecode(code));
            if (result.Succeeded)
            {
                return;
            }
            else
            {
                lblError.Text = result.Errors.FirstOrDefault();
                txtNewPassword.TextMode= TextBoxMode.SingleLine;
                txtNewPassword.Text = "Error contact support";
                txtNewPassword2.TextMode= TextBoxMode.SingleLine;
                txtNewPassword2.Text = result.Errors.FirstOrDefault();
                txtNewPassword.Enabled = false;
                txtNewPassword2.Enabled = false;
                imageButton1.Enabled = false;
            }
        }
        else
        {
            lblError.Text = "Account Does Not Exist";
            imageButton1.Enabled = false;
        }
    }
}

【问题讨论】:

  • 构象链接是什么样的?你是在做重定向还是点击事件?
  • 开始记录创建的令牌和请求的令牌,成功和失败,看看有什么区别。这可能会暗示正在发生的事情。
  • 您是否验证了用户使用的链接是完全有效的链接?电子邮件因破坏文本块而臭名昭著,一些电子邮件客户端标记过程将从损坏的参考链接创建一个链接。这意味着用户通常会拥有正确的基本 URL,但传入的参数或令牌损坏。

标签: c# asp.net asp.net-identity-2


【解决方案1】:

现场演示项目

我为您创建了一个精简的演示项目。它托管在GitHub here 上,并在Azure here 上直播。它按设计工作(请参阅有关 Azure 网站的编辑),并使用与您使用的方法相似但不相同的方法。

它以 this tutorial 开头,然后我删除了这个 NuGet 演示代码附带的杂乱无章:

Install-Package -Prerelease Microsoft.AspNet.Identity.Samples 

出于您的目的,my demo code 比 NuGet 示例更相关,因为它只关注令牌创建和验证。特别是看看这两个文件:

Startup.Auth.cs.

每次应用程序启动时,我们只实例化一次 IDataProtectionProvider

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

    public void ConfigureAuth(IAppBuilder app)
    {
        DataProtectionProvider = 
            new DpapiDataProtectionProvider("WebApp2015");

        // other code removed
    }
}

AccountController.cs.

然后在AccountController 中,我们使用静态提供程序而不是创建新的提供程序。

userManager.UserTokenProvider = 
    new DataProtectorTokenProvider<User>(
        Startup.DataProtectionProvider.Create("UserToken"));

这样做可能会消除您看到的错误。以下是您在进一步排除故障时需要考虑的一些问题。

您是否使用了两个不同的UserTokenProvider 目的?

DataProtectorTokenProvider.Create(string[] purposes) 方法采用 purposes 参数。以下是 MSDN 对此的看法:

目的。用于确保受保护数据的额外熵可能仅出于正确目的而不受保护。

当您创建用户 code 时,您正在使用(至少)两个不同的目的:

  1. user.Id
  2. "ConfirmUser"
  3. 您使用GetOwinContext()... 检索的ApplicationUserManager 的用途。

这是您作为 sn-p 的代码。

userManager.UserTokenProvider = 
    new DataProtectorTokenProvider<User>(provider.Create(user.Id));

manager.UserTokenProvider = 
    new DataProtectorTokenProvider<User>(provider.Create("ConfirmUser"));

string code = Context
    .GetOwinContext()
    .GetUserManager<ApplicationUserManager ()      
    .GenerateEmailConfirmationToken(user.Id)

当您验证code 时,您可能使用了错误的用途。您在哪里为用于确认电子邮件的ApplicationUserManager 分配UserTokenProvider?它的目的论点必须相同!

var manager = Context.GetOwinContext()
                  .GetUserManager<ApplicationUserManager>();

var result = manager.ConfirmEmail(userId, HttpUtility.UrlDecode(code));

令牌无效的可能性很大,因为您有时使用不同的UserTokenProvider 创建目的而不是用于验证。

为什么有时会这样?彻底搜索您的代码以找到分配给UserTokenProvider 的所有位置。也许您在某个意外的地方(例如在属性或 IdentityConfig.cs 文件中)覆盖它,使其看起来是随机的。

TokenLifespan 过期了吗?

您提到了 Invalid Token 消息是随机出现的。可能是令牌已过期。 This tutorial 指出默认寿命是一天。你可以这样改变它:

manager.UserTokenProvider = 
    new DataProtectorTokenProvider<ApplicationUser>
      (dataProtectionProvider.Create("WebApp2015"))
      {                    
         TokenLifespan = TimeSpan.FromHours(3000)
      };

为什么是三个UserManager 实例?

以下是创建确认令牌的代码中的一些 cmets。您似乎正在使用三个独立的 UserManager 实例,包括派生的 ApplicationUserManager 类型。这是关于什么的?

  1. 这里manager的类型是什么?
  2. 为什么要创建userManager 而不是使用现有的manager
  3. 为什么使用manager.UserTokenProvider 而不是userManager.UserTokenProvider
  4. 为什么要从 Context 获得第三个 UserManager 实例?

请注意,我删除了很多代码,只专注于您的令牌创建。

// 1. 
IdentityResult result = manager.Create(user, "Password134567");

if (result.Succeeded)
{
    var provider = new DpapiDataProtectionProvider("WebApp2015");

    // 2. 
    UserManager<User> userManager = 
        new UserManager<User>(new UserStore<User>());

    userManager.UserTokenProvider = 
        new DataProtectorTokenProvider<User>(provider.Create(user.Id));

    // 3.
    manager.UserTokenProvider = 
        new DataProtectorTokenProvider<User>(provider.Create("ConfirmUser"));

    // 4. 
    string raw = Context.GetOwinContext()
                 .GetUserManager<ApplicationUserManager>()
                 .GenerateEmailConfirmationToken(user.Id)

    // remaining code removed
}

我想知道我们是否可以将上述简化为仅使用一个UserManager 实例,如下所示。

// 1. 
IdentityResult result = manager.Create(user, "Password134567");

if (result.Succeeded)
{
    var provider = new DpapiDataProtectionProvider("WebApp2015");

    manager.UserTokenProvider = 
        new DataProtectorTokenProvider<User>(provider.Create(user.Id));

    // 3.
    var provider = provider.Create("ConfirmUser");
    manager.UserTokenProvider = 
        new DataProtectorTokenProvider<User>(provider);

    // 4. 
    string raw = manager.GenerateEmailConfirmationToken(user.Id);

    // remaining code removed
}

如果您使用这种方法,请务必在确认电子邮件时使用相同的 "ConfirmUser" 目的参数。

IdentityHelper 里面有什么?

由于错误是随机发生的,我突然想到IdentityHelper 方法可能对code 做了一些时髦的事情,从而搞砸了事情。这些方法中的每一个都有什么?

  • IdentityHelper.GetUserConfirmationRedirectUrl()
  • IdentityHelper.RedirectToReturnUrl()
  • IdentityHelper.GetCodeFromRequest()
  • IdentityHelper.GetUserIdFromRequest()

我可能会编写一些测试来确保您的进程创建的原始code 始终与您的进程从Request 检索的原始code 匹配。在伪代码中:

var code01 = CreateCode();
var code02 = UrlEncode(code01);
var request = CreateTheRequest(code02);
var response = GetTheResponse();
var code03 = GetTheCode(response);
var code04 = UrlDecode(code03);
Assert.AreEquals(code01, code04);

以上运行10000次,确保不存在问题。

结论

我强烈怀疑问题在于在创建令牌期间使用一个 purposes 参数,而在确认期间使用另一个参数。只使用一个目的,你可能会没事。

在 Azure 网站上进行这项工作

  1. 使用 SqlCompact 代替 localdb。
  2. 使用 app.GetDataProtectionProvider() 而不是 DpapiDataProtectionProvider,因为 Dpapi 不适用于网络农场。

【讨论】:

  • TokenLifespan 加 1!
【解决方案2】:

网站是否托管在多个网络服务器上? 如果是这样,您不能在此处使用 DPAPI。它是特定于机器的。 您需要使用其他数据保护提供商。

【讨论】:

    【解决方案3】:

    代码(令牌)中可能有一些 url 无效字符。因此,当它出现在任何 url 中时,我们需要使用 HttpUtility.UrlEncode(token)HttpUtility.UrlDecode(token)

    在此处查看详细信息: Identity password reset token is invalid

    【讨论】:

    【解决方案4】:

    虽然我认为 Shaun 为解决此类问题提供了很好的反馈。您发表的一条评论让我认为这可能是备用令牌问题。另请参阅有关多个服务器和令牌的 cmets。

    ...纯属随机

    我不认为它是随机的;-)。但是对于一些用户来说,是什么让它在大部分时间都可以正常工作。

    为不同的页面或APP生成了令牌,或者来自同一个APP并且刚刚过期。例如,令牌在短时间内有效。存在于浏览器中。令牌来自类似的应用程序或来自不同表单/页面上的同一应用程序。 自从为域生成令牌后,浏览器就会显示令牌。

    但在分析时,令牌不匹配,或者刚刚过期。

    考虑令牌的生命周期。

    【讨论】:

      猜你喜欢
      • 2020-09-01
      • 2014-07-12
      • 2014-10-13
      • 1970-01-01
      • 2016-05-03
      • 2015-04-28
      • 2020-12-15
      • 2015-01-06
      • 2015-07-17
      相关资源
      最近更新 更多