首页 文章

为什么我的用于ASP.NET身份的Couchbase自定义存储提供程序不会保留更改?

提问于
浏览
0

上下文

我正在实现一个单点登录服务器,其中用户,角色,声明等将保留在Couchbase中 . 到目前为止,我的步骤是:

当实施步骤1-3时,一切都很完美 . 用户通过EF存储在LocalDB实例(IdentityServer3.AspNetIdentity的默认实现)中的常用 AspNetUsers 表中,我可以使用IdentityManager UI添加,编辑和删除用户 .

第4步涉及implementing custom store classes,这些基本上直接来自GitHub上的couchbase-aspnet-identity项目,并进行了一些小的本地更改(例如实现 UserStore.Users getter,其中throws NotImplementedException in the original code) .

问题

现在,当我创建一个用户时,它按预期存储在Couchbase中 . 如果我编辑添加到自定义 ApplicationUser 类的自定义字段(例如 FirstNameAge ),则更改将保持正确 . 到现在为止还挺好 .

但是,如果我编辑用户的密码,电子邮件地址或电话号码(在IdentityManager UI中),则会发生以下情况:

  • IdentityManager UI显示成功消息,说明更改成功(参见下文)

  • 但是,编辑的字段不反映更改(也见下文)

  • Couchbase中没有保留更改,因此很明显当重新加载用户时,更改未显示

单步执行代码,我发现对我的 UserStore.UpdateAsync 方法进行了多次调用:

public async Task UpdateAsync(T user)
{
    await _bucket.UpdateAsync(user.Id, user);
}

在每次调用时,用户都被正确地保存到Couchbase . 所以实际上问题不在于变化根本不存在,而是它们被持久化然后被原始值覆盖 .

例如,如果我更改用户的电话号码,则会调用该方法三次 . 将它从空更改为123,我的 user 对象中的相关字段如下:

Call 1

  • PhoneNumber123 (在UI中输入的新值)

  • PhoneNumberConfirmed :假

Call 2

  • PhoneNumber :123

  • PhoneNumberConfirmedtrue

Call 3

  • PhoneNumber :null(原始值)

  • PhoneNumberConfirmed :false(原值)

(该对象每次都有不同的 SecurityStamp 值,否则对象值相同 . )

当我更改电子邮件地址或密码时,会发生同样的事情 . (对于密码,只有两个调用,但在每种情况下,最后一个调用会将所有字段重置为其原始值 . )

这个问题

What is causing the extra call to UserStore.UpdateAsync() and how do I fix it?

不幸的是我无法在调用堆栈中向上移动:我认为 UserStore 是从 UserManager 调用的,它是 Microsoft.AspNet.Identity.Core 的一部分,我没有源代码 .

额外细节

我的UserStore类实现了所讨论的所有可选接口here,即它看起来像这样:

public class UserStore<T> :
    IUserLoginStore<T>,
    IUserClaimStore<T>,
    IUserRoleStore<T>,
    IUserSecurityStampStore<T>,
    IQueryableUserStore<T>,
    IUserPasswordStore<T>,
    IUserPhoneNumberStore<T>,
    IUserStore<T>,
    IUserLockoutStore<T, string>,
    IUserTwoFactorStore<T, string>,
    IUserEmailStore<T>
    where T : IdentityUser
{
    // ...
}

这是我刚刚将电话号码更改为123时IdentityManager UI的屏幕截图 . (注意成功消息,但空电话字段 . )

enter image description here

2 回答

  • 0

    我设法深究这一点 . 事实上,它与默认的EF实现(我的问题中的第3步)一起工作但是当我将它换成Couchbase存储提供程序时失败意味着我最初怀疑Couchbase提供程序有问题(特别是因为它基于开发人员预览代码) .

    实际上问题出在IdentityManager.AspNetIdentity包中,特别是方法AspNetIdentityManagerService.SetUserPropertyAsync()

    public virtual async Task<IdentityManagerResult> SetUserPropertyAsync(string subject, string type, string value)
    {
        TUserKey key = ConvertUserSubjectToKey(subject);
        var user = await this.userManager.FindByIdAsync(key);
    
        // [...]
    
        var metadata = await GetMetadataAsync();
        var propResult = SetUserProperty(metadata.UserMetadata.UpdateProperties, user, type, value);
        if (!propResult.IsSuccess)
        {
            return propResult;
        }
    
        var result = await userManager.UpdateAsync(user);
        if (!result.Succeeded)
        {
            return new IdentityManagerResult(result.Errors.ToArray());
        }
    
        return IdentityManagerResult.Success;
    }
    

    如果你进入 SetUserProperty() 电话足够远,你会看到AspNetIdentityManagerService.SetPhone(),如下所示:

    public virtual IdentityManagerResult SetPhone(TUser user, string phone)
    {
        var result = this.userManager.SetPhoneNumber(user.Id, phone);
    
        // [...]
    }
    

    因为我们只将用户ID传递给 SetPhoneNumber() ,所以 UserManager 要求 UserStore 用户的另一个实例(通过调用 UserStore.FindByIdAsync() ),因此 PhoneNumber 属性永远不会在传递给 SetUserProperty() 的实例上更新 . 因此,当我们在 SetUserPropertyAsync() 中调用 userManager.UpdateAsync(user) 时,我们将传入一个陈旧的对象而不应用更改 .

    据推测,EF确保了两个实例这里使用的是同一个,但是其他提供商将Couchbase提供商与其他更成熟的实现(例如AspNet.Identity.Mongo)进行了比较,并且Couchbase代码看起来很合理 .

  • 0

    我遇到了同样的问题,这个线程让我理解为什么IdentityManager会多次调用此调用 . 我也把我的DAL交换到非sql商店 .

    我的解决方案有些类似 . 我在更新后将用户模型存储在临时缓存中,另外,我使用Guid类型的实例级变量作为缓存键的transactionId . 我还将UserManager生命周期更改为InstancePerHttpRequest,以确保对Update方法进行的两个并发调用(在本例中)来自同一个http请求,因此将共享相同的transactionId . 我没有遇到随后的http请求和使用缓存的任何竞争条件 .

相关问题