Как я могу использовать один и тот же экземпляр UserManager в разных проектах?

#asp.net-core #asp.net-identity #blazor #identityserver4

Вопрос:

Так что у меня есть Asp.Net Основной веб-проект Mvc IdentityServer, другой веб-проект для управления IdentityServer(администратор) и проект веб-api IdentityServer.

Все они находятся в одном и том же решении Visual studio.

Как сервер идентификации, так и проекты Api имеют свои собственные классы Startup.cs, в которых я регистрирую личность следующим образом

 services.AddIdentity<EntityUser, EntityRole>()
            .AddRoles<EntityRole>()
            .AddEntityFrameworkStores<IdentityServerDbContext>()
            .AddDefaultTokenProviders();
 

Проект администратора регистрирует HttpClient в своей программе.cs для связи с веб-api

 builder.Services.AddHttpClient("IdentityServerApi",
           client => client.BaseAddress = new Uri(_BASEADDRESS))
          .AddHttpMessageHandler(sp =>
          {
              var handler = sp.GetService<AuthorizationMessageHandler>()
                  .ConfigureHandler(
                      authorizedUrls: new[] { ApplicationConsts.ApiBaseUrl },
                      scopes: new[] { ApplicationConsts.ApiName });
              return handler;
          });

        builder.Services.AddTransient(sp => sp.GetRequiredService<IHttpClientFactory>()
            .CreateClient("IdentityServerApi"));
 

То, что я пытаюсь сделать, — это заставить пользователя сбросить свой пароль при входе в систему.
Поэтому для достижения этой цели в действии контроллера учетной записи/входа в систему проекта IdentityServer я создаю токен и перенаправляю пользователя на страницу сброса пароля в проекте администратора:

 if (isForcePasswordResetRequired)
            {                  
                if (hasUserDoneForcePasswordReset)
                {
                    return await SignInTheUser(model, user, context);
                }
                else
                {
                    var result = await _signInManager.CheckPasswordSignInAsync(user, model.Password, false);

                    if (result.Succeeded)
                    {
                        // show him password reset form 
                        var token =  await _userManager.GeneratePasswordResetTokenAsync(user);
                        token = HttpUtility.UrlEncode(token);
                        var callbackUrl = ApplicationConsts.AdminBaseUrl   $"/Account/ResetPassword?userId={user.Id}amp;token={token}";
                       
                        return Redirect(callbackUrl);
                       
                    }
                }
            }
            else
            {
                return await SignInTheUser(model, user, context);
            } 
 

Как только пользователь перенаправлен на страницу сброса пароля (которая находится в проекте администратора), ему предлагается ввести новый пароль и подтвердить новый пароль. При отправке вызывается следующая функция для пересылки информации на уровень Api:

 public async Task SavePassword()
{
    ResetPasswordDto resetPasswordDto = new ResetPasswordDto();
    resetPasswordDto.UserId = userId_par;
    resetPasswordDto.Token = HttpUtility.UrlEncode(token_par);
    resetPasswordDto.Password = userPasswordDto.Password;

    HttpResponseMessage httpResponse;
    var url = $"{ApplicationConsts.ApiBaseDnsUrl}/Account/user/resetpassword";
    httpResponse = await HttpClientService.AnonymousHttpClient.PostAsJsonAsync<ResetPasswordDto>(url, resetPasswordDto);

    if (httpResponse.IsSuccessStatusCode)
    {

        toastService.ShowSuccess("Password changed successfully. Please log in using your new password.");
        NavManager.NavigateTo("AdminBaseUrl");
    }
    else
    {
        toastService.ShowError("Something went wrong.");
    }
}
 

оттуда вызывается действие Api, которое вызывает метод репозитория для сброса пароля, как показано ниже:

 public virtual async Task<IdentityResult> UserResetPassword(string userId, string token, string newPassword)
    {   
        token = HttpUtility.UrlDecode(token);
        var user = await userManager.FindByIdAsync(userId);
        var res = await userManager.ResetPasswordAsync(user, token, newPassword);
        if (res.Succeeded)
        {
            return res;
        }
        else throw new Exception(string.Join(",", res.Errors.Select(x=> x.Code)));
    }
 

Проблема, с которой я сталкиваюсь, заключается в том, что токен не проверяется, даже если токен, сгенерированный в IdentityServer, идентичен тому, который передается выше в метод ResetPasswordAsync. Штамп безопасности не является нулевым.

Я предполагаю, что это как-то связано с тем фактом, что токен был сгенерирован в проекте IdentityServer, который использует свой собственный экземпляр UserManager, а затем я пытаюсь проверить его через проект Api, у которого снова есть собственный экземпляр UserManager.

Если это так, как я могу использовать экземпляр UserManager в разных проектах, в данном случае в проектах IdentityServer и Api?

Комментарии:

1. Нет, ты не можешь так поступать, и ты не должен так поступать. Вы должны защитить свои конечные точки API с помощью аутентификации по токену Jwt; то есть вы должны передать токен Jwt, выданный сервером идентификации, в HTTP-вызовах конечным точкам. Эта процедура может быть прозрачной и автоматической в зависимости от выбранного вами шаблона проекта VS. В противном случае вам потребуется вручную добавить токен jwt в заголовок авторизации вашей службы HttpClient.

2. Прежде чем продолжить свой проект с помощью IdentityServer, убедитесь, что вы прочитали эту статью ( devblogs.microsoft.com/aspnet/… ) корпорацией Майкрософт и сопровождающими ее комментариями. Лично я покончил с сервером идентификации.