Что может привести к тому, что `UserManager` вернет не того пользователя?

На моем сайте ASP.NET Core 2.1.0 MVC происходит что-то довольно страшное. Пока я просматривал сайт, вдруг выяснилось, что я вошел в систему как другой пользователь (который также просматривал сайт в это время).

Я не могу точно определить, есть ли конкретный вариант использования, который вызывает это, но это произошло уже дважды. При переходе на другие страницы по-прежнему отображается, что я вошел в систему как другой пользователь. Мне даже кажется, что я принимаю претензии пользователя, под которым я неправильно вошел в систему.

Мой вопрос: из-за чего это могло произойти?


EDIT: с тех пор я изменил userManager и notificationService на "ограниченный", и эта проблема возникла снова, поэтому потенциальная проблема, описанная здесь, не может быть причиной.

Пытаясь разобраться в этом, я считаю, что виновником может быть следующий вызов в _Layout.cshtml:

@inject UserManager<ApplicationUser> userManager
@inject NotificationService notificationService
@inject CommunityService communityService
@{
    ApplicationUser user = await userManager.GetUserAsync( User );
}

Возвращенный user используется для отображения информации о пользователе и вызовов notificationService и communityService. Они также показывали данные, относящиеся к неправильному (не мне) пользователю.

Если это имеет значение, вот как ApplicationDbContext настроен в Startup.cs:

// Add framework services.
services
    .AddDbContext<ApplicationDbContext>( options => options
        .UseLazyLoadingProxies()
        .UseSqlServer(_configuration.GetConnectionString( "DefaultConnection" ) ) );
services
    .AddIdentity<ApplicationUser, IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders();

Я вспомнил, что 'scoped' — рекомендуемое время жизни, которое следует использовать при регистрации Entity Framework для внедрения зависимостей. Однако и NotificationService, и CommunityService регистрируются как "переходные" и запрашивают ApplicationDbContext через внедрение конструктора для доступа к данным.

services.AddTransient<CommunityService, CommunityService>();
services.AddTransient<NotificationService, NotificationService>();

Может ли это иметь какое-то отношение к этому? В настоящее время я не понимаю, может ли это иметь какое-либо значение. Кажется, я не могу воспроизвести эту проблему.


comment
Насколько мне известно, если вы не получите доступ к куки-файлу аутентификации другого пользователя, выдать себя за пользователей будет довольно сложно. Как вы это тестируете, два экземпляра на одной машине или на двух разных машинах? Я предполагаю, что «AddTransient» и «AddScoped» не имеют ничего общего с этой проблемой.   -  person Thangadurai    schedule 28.07.2018
comment
@Thangadurai Это произошло на двух разных машинах в реальной среде.   -  person Steven Jeuris    schedule 28.07.2018
comment
Любые кешированные части, которые не понимают, что данные изменились, и не обновляются или что-то в этом роде?   -  person Sami Kuhmonen    schedule 28.07.2018
comment
@SamiKuhmonen Не то, чтобы я знал об этом. UserManager<ApplicationUser> — это значение по умолчанию, предоставляемое MVC, и я просто показываю user.DisplayName (которое отображается как «не я»). Кроме того, я не могу войти в систему как другой пользователь (например, у меня нет pwd или аутентификации Google, в зависимости от того, что он использует).   -  person Steven Jeuris    schedule 28.07.2018
comment
Вы находитесь за каким-либо CDN или прокси?   -  person Grimm    schedule 28.07.2018
comment
@SamiKuhmonen Извините. По-видимому, DisplayName уже является свойством, которое я добавил сам, но это простое свойство с геттером и сеттером, которые я добавил к ApplicationUser : IdentityUser по умолчанию.   -  person Steven Jeuris    schedule 28.07.2018
comment
@Grimm Когда произошла ошибка, я был на своем мобильном телефоне. Сеть 4G от Oister в Дании, если это вам что-то говорит. :) Другой пользователь (мой брат) был в Бельгии.   -  person Steven Jeuris    schedule 28.07.2018
comment
@Thangadurai Мне кажется, что инфраструктура сущностей является наиболее вероятным виновником здесь, поскольку время жизни возвращаемых объектов управляется инфраструктурой сущностей. Если (по какой-то причине) время жизни/кеширования увеличивается между запросами, это может произойти.   -  person Steven Jeuris    schedule 28.07.2018
comment
@StevenJeuris Хорошо, я думаю, тогда мы можем исключить эту теорию ...   -  person Grimm    schedule 28.07.2018
comment
Возвращенный пользователь используется для отображения информации о пользователе и выполнения вызовов notificationService и communityService... Можете ли вы показать, как эти службы используют этот объект User?   -  person Grimm    schedule 28.07.2018
comment
@Гримм, конечно. В первую очередь они запрашивают ApplicationDbContext, выполняя сопоставление идентификаторов, например, _data.Notifications.Where( n => n.UserId == user.Id ). Однако у меня есть один метод, который фактически устанавливает свойство переданного пользовательского объекта и вызывает _data.SaveChangesAsync() в notificationService. Это вызывается с другой страницы, чем Layout (Уведомления). Имеет ли это значение?   -  person Steven Jeuris    schedule 28.07.2018
comment
Хм, мне кажется все нормально. Я сейчас довольно беспомощен...   -  person Grimm    schedule 28.07.2018
comment
Обновлен до ASP.NET 2.1.2 сейчас и использует «ограниченный» для двух служб вместо «переходный». Я надеюсь, что никто не столкнется с этой ошибкой, но я понятия не имею, почему эти изменения должны что-то изменить. Я обновлю, если получу сообщение, что это произошло снова.   -  person Steven Jeuris    schedule 28.07.2018
comment
ApplicationUser user = await userManager.GetUserAsync( User ); - Какой здесь параметр User? Обычно я использую утверждение «sub», которое является уникальным идентификатором текущего пользователя, и использую FindById для получения сведений о пользователе. Переходный объект более ограничен, т. Е. Каждое место, где требуется объект, новый экземпляр предоставляется инфраструктурой DI, но с «областью действия» один и тот же объект используется совместно, но для этого одного запроса.   -  person Thangadurai    schedule 29.07.2018
comment
@Thangadurai User предоставляется MVC и определяется в RazorPageBase как public virtual ClaimsPrincipal User { get; }.   -  person Steven Jeuris    schedule 29.07.2018
comment
Это произошло снова, на этот раз с другим пользователем. :O На самом деле, я был неправ, похоже, я также беру на себя претензии пользователя, под которым я неправильно залогинился. :О   -  person Steven Jeuris    schedule 30.07.2018


Ответы (1)


Хорошие новости! Я был причиной этого сам (думаю, помогите мне понять это, прочитав подробности ниже). Таким образом, вы можете быть уверены, что если вы не совершаете ту же ошибку, что и я, механизм аутентификации ASP MVC здесь не виноват (по крайней мере, это мое текущее понимание).

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

Вкратце: я позвонил SignInManager<TUser>.RefreshSignInAsync(TUser) с "неправильным" пользователем (тот, под которым я вошел в систему), из-за чего я вошел в систему как этот пользователь.

Почему я это сделал? В моем конкретном случае я хотел передать претензию другому пользователю на основе действий пользователя, вошедшего в систему в данный момент. Поэтому я позвонил:

await _userManager.AddClaimAsync( userToGiveClaim, newClaim );
await _signInManager.RefreshSignInAsync( userToGiveClaim );

Я позвонил RefreshSignInAsync, так как хотел, чтобы пользователь, получивший претензию, не выходил из системы и не входил в нее, чтобы новая претензия вступила в силу. Из RefreshSignInAsync документация У меня сложилось впечатление, что это должно работать:

Восстанавливает файл cookie приложения пользователя, сохраняя при этом существующие AuthenticationProperties, такие как RememberMe, в качестве асинхронной операции.

Параметры user Пользователь, чей файл cookie для входа должен быть обновлен.

Мне до сих пор не совсем понятно, почему пользователь, который в данный момент вошел в систему, когда этот вызов запускается, получает идентификатор пользователя, переданного этому вызову. Это все еще не так, как я понимаю документацию (я подал это как отчет об ошибке), но поскольку это воспроизводимо, я теперь более склонен полагать, что просто неправильно понял документацию.

person Steven Jeuris    schedule 30.07.2018
comment
Это имеет смысл для меня. github.com/aspnet/Identity/issues/1831 github.com/aspnet/Identity/blob/master/src/Identity/ Это повторно создает файл cookie пользователя с обновленными утверждениями. Если вы вызовете этот метод с другим пользователем, он выйдет из системы и войдет в систему с переданным пользователем. - person Alex; 09.10.2018