使用 IdentityServer4 从多个 API 创建用户

Dav*_*is4 6 c# asp.net-identity asp.net-core identityserver4

所以我一直在为这个问题痛心疾首。

我们有一个 Web 应用程序,它使用IdentityServer4AspNetIdentity来验证和注册用户(这是按预期工作的)。此外,我们还有其他 API(在同一解决方案中),可以使用 IdentityServer4 对访问 API 的用户进行身份验证

然而,问题是,除了身份验证,我们不能使用 API 来创建新用户

例如,用户应该能够通过 Web API 而不仅仅是从 Web 应用程序创建其他用户,因为在我们的例子中,用户链接到其他用户(将其视为多个配置文件)。

我不太熟悉 .Net Core 框架提供的所有配置服务,我尝试了多种通过 API 访问 Web 应用程序的用户管理器以通过经典 POST 请求注册我的用户的方法,但似乎没有任何效果. 在线搜索很棘手,因为我们的问题非常具体,这就是我在这里发帖的原因。

API Startup.cs - 配置服务:

services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
     // base-address of your identityserver
     options.Authority = Configuration["IdentityServer:Url"];

     // name of the API resource
     options.ApiName = Configuration["IdentityServer:APIName"];
     options.ApiSecret = Configuration["IdentityServer:APISecret"];

     options.EnableCaching = true;
     options.CacheDuration = TimeSpan.FromMinutes(10); // that's the default

     options.RequireHttpsMetadata = Convert.ToBoolean(Configuration["IdentityServer:RequireHttpsMetadata"]);
});
Run Code Online (Sandbox Code Playgroud)

API Startup.cs - 配置:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseCors("AllowAllOrigins");
    app.UseAuthentication();
    app.UseMvc();
}
Run Code Online (Sandbox Code Playgroud)

API UsersController.cs - 构造函数:

private readonly UserManager<ApplicationUser> _userManager;
private readonly ApplicationDbContext _context;

public UsersController(IUserService service,
ApplicationDbContext context,
UserManager<ApplicationUser> userManager)
{
    _service = service;
    _userManager = userManager;
    _context = context;
}
Run Code Online (Sandbox Code Playgroud)

现在的问题是,当我启动 API 并尝试访问 UsersController 时,出现以下错误:

System.InvalidOperationException: Unable to resolve service for type 'Microsoft.AspNetCore.Identity.UserManager`1[XXXXX.Data.Models.ApplicationUser]' while attempting to activate 'XXXXXXX.Api.Controllers.UsersController'.
Run Code Online (Sandbox Code Playgroud)

我真诚地希望我至少能找到一些关于如何进行的建议。

如果有什么不清楚的地方请回复,我会很乐意添加更多信息或把事情说清楚。

亲切的问候,

马里奥。

编辑: 谢谢大家的回复。@Vidmantas 下面提供的代码片段做到了。

由于我对 .net core 的了解有限,我在配置服务功能中做了很多尝试和错误,正如您想象的那样,但没有奏效。我坚信使用 .net core 有点容易(例如 API),但是当涉及到配置服务时,复杂性(主要是令人费解/令人困惑)会爆炸。

至于架构,你给了我未来重构的好主意。记笔记。

马里奥。

Vid*_*ius 18

如果我对您的理解正确,那么您实际上不应该通过 API 创建用户——这就是您安装 Identity Server 4 的原因——为您的用户群提供中央授权进行身份验证。你真正需要的是:

  • Identity Server 4 端的一组 API 端点,用于管理 AspNetIdentity
  • 全新的 API,但它与 Identity Server 4 共享相同的数据库,用于您的 AspNetIdentity
  • 让您的 API 共享 AspNet Identity 的数据库

如果你选择最后一个选项,那么你可能需要像下面这样添加:

services.AddDbContext<IdentityContext>(); //make sure it's same database as IdentityServer4

services.AddIdentityCore<ApplicationUser>(options => { });
new IdentityBuilder(typeof(ApplicationUser), typeof(IdentityRole), services)
    .AddRoleManager<RoleManager<IdentityRole>>()
    .AddSignInManager<SignInManager<ApplicationUser>>()
    .AddEntityFrameworkStores<IdentityContext>();
Run Code Online (Sandbox Code Playgroud)

这将为您提供足够的服务来使用UserManager它,并且不会设置任何不必要的身份验证方案。

由于关注点分离,我不推荐最后一种方法 - 您的 API 应该关注提供资源,而不是创建用户和提供资源。在我看来,第一种和第二种方法没问题,但我总是倾向于为 AspNetIdentity 管理提供干净的单独服务。

来自我的一个项目的示例架构,我们在其中实施了这种方法:

  • auth.somedomain.com - IdentityServer4 Web 应用程序,带有用于用户身份验证的 AspNetIdentity。
  • account.somedomain.com - 带有 AspNetIdentity(与 Identity Server 4 相同的数据库)的 AspNetCore Web 应用程序,用于 AspNetIdentity 用户管理
  • webapp1.somedomain.com - 所有前端逻辑所在的 Web 应用程序(当然也可以有后端,如果 AspNetCore MVC 或类似的东西)
  • api1.somedomain.com - 纯粹用于 API 目的的 Web 应用程序(如果您将单个应用程序用于前端和后端,那么您可以结合最后两个应用程序)


DaI*_*mTo 7

我和你有类似的情况。

  • 具有 asp .net 身份用户的身份服务器。(DB 包含客户和用户数据)
  • API(数据库包含对应用程序数据的访问).net Framework
  • 应用程序.net 框架。

我们的用例是通常会通过身份服务器创建新用户。但是,我们还希望应用程序能够邀请用户。所以我可以登录到应用程序,我想邀请我的朋友。这个想法是邀请的行为就像用户创建自己一样。

所以它会向我的朋友发送一封附有代码的电子邮件,然后用户就可以提供他们的密码并拥有一个帐户。

为此,我在帐户控制器上创建了一个新操作。

[HttpGet]
    [AllowAnonymous]
    public async Task<IActionResult> Invited([FromQuery] InviteUserRequest request)
    {

        if (request.Code == null)
        {
            RedirectToAction(nameof(Login));
        }
        var user = await _userManager.FindByIdAsync(request.UserId.ToString());
        if (user == null)
         {
          return View("Error");
        }

        var validateCode = await _userManager.VerifyUserTokenAsync(user, _userManager.Options.Tokens.PasswordResetTokenProvider, "ResetPassword", Uri.UnescapeDataString(request.Code));
        if (!validateCode)
        {
         return RedirectToAction(nameof(Login), new { message = ManageMessageId.PasswordResetFailedError, messageAttachment = "Invalid code." });
        }

        await _userManager.EnsureEmailConfirmedAsync(user);
        await _userManager.EnsureLegacyNotSetAsync(user);

        return View(new InvitedViewModel { Error = string.Empty, Email = user.Email, Code = request.Code, UserId = user.Id });
    }
Run Code Online (Sandbox Code Playgroud)

当用户接受电子邮件时,我们添加他们。

[HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Invited([FromForm] InvitedViewModel model)
    {
        if (!ModelState.IsValid)
        {
            model.Error = "invalid model";
            return View(model);
        }

        if (!model.Password.Equals(model.ConfirmPassword))
        {

            model.Error = "Passwords must match";
            return View(model);
        }
        if (model.Terms != null && !model.Terms.All(t => t.Accept))
        {
            return View(model);
        }
        var user = await _userManager.FindByEmailAsync(model.Email);
        if (user == null)
        {             
            // Don't reveal that the user does not exist
            return RedirectToAction(nameof(Login), new { message = ManageMessageId.InvitedFailedError, messageAttachment = "User Not invited please invite user again." });
        }

        var result = await _userManager.ResetPasswordAsync(user, Uri.UnescapeDataString(model.Code), model.Password);

        if (result.Succeeded)
        {            
            return Redirect(_settings.Settings.XenaPath);
        }

        var errors = AddErrors(result);
                    return RedirectToAction(nameof(Login), new { message = ManageMessageId.InvitedFailedError, messageAttachment = errors });
    }
Run Code Online (Sandbox Code Playgroud)

这样做的原因是只有身份服务器应该读取和写入其数据库。api 和第三方应用程序永远不需要直接更改由另一个应用程序控制的数据库。所以通过这种方式,API 告诉身份服务器邀请用户,然后身份服务器自己控制其他一切。

此外,通过这种方式,您无需在 API 中使用用户管理器 :)