如何与 IdentityServer4 中的非标准用户存储接口?

How to interface with non-standard User store in IdentityServer4?

想用IdentityServer4实现OIDC认证,方便以后开发。我们有一个在 Asp.Net 成员资格或 Aspnet Identity 之前实施的现有本土成员资格模式。我们的遗留应用程序使用现有的成员资格架构来验证和授权用户。

如何为用户存储实现指向旧架构的适配器(假设用户 table 具有用户名和密码列)?我见过的唯一示例是 InMemory 用户和 AspnetIdentity。

要与您自己的用户存储集成,您需要创建 IProfileService 的实现(如果您需要使用资源所有者授权类型,还可以选择 IResourceOwnerPasswordValidator),然后在 IServiceCollection.

例如:services.AddTransient<IProfileService, GabeFcCustomProfileService>();

我需要适应 IdentityServer4 的遗留系统需要三个信息来验证用户:公司标识符、用户名和密码。

我们的系统有一个带有公司 table 的主数据库,其中包含 company-specific 数据库(租户)的连接字符串。每个租户数据库都有一个用户 table,其中包含用户配置文件信息,包括用户名和密码。

使用 Scott Brady 的回答,我能够实现适合我们情况的 IResourceOwnerPasswordValidator,但并非没有深入研究 IdentityModel.TokenClient 源代码。我发现令牌端点 (token_endpoint: "http:///connect/token") 采用 URL 编码形式并序列化 key/value 对并使它们在 IResourceOwnerPasswordValidator.ValidateAsync 中可用ResourceOwnerPasswordValidationContext 参数的 Request.Raw 属性。

这是我的 ResourceOwnerPasswordValidator.ValidateAsync() 实施示例,显示了我如何使用用户名和密码以外的其他参数进行身份验证:

public Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{
     _logger.LogInformation("Begin resource owner password validation.");

     var username = context.UserName;
     var password = context.Password;
     var company = context.Request.Raw.Get("company");

     if (string.IsNullOrWhiteSpace(company))
     {
          _logger.LogError("'company' doesn't exist in ResourceOwnerPasswordValidationContext.Request.Raw collection.");
          context.Result = new GrantValidationResult(TokenRequestErrors.InvalidRequest, "Company is required.");
           return Task.FromResult(0);
      }

      if (string.IsNullOrWhiteSpace(username))
      {
           _logger.LogError("'username' is null or whitespace.");
           context.Result = new GrantValidationResult(TokenRequestErrors.InvalidRequest, "Username is required.");
           return Task.FromResult(0);
      }

      var userRepo = _userRepositoryFactory.GetUserRepositoryForCompany(company);
      var user = userRepo.GetUserByUserId(username);

      if (user == null)
      {
           _logger.LogError($"No user found for company {company} and username {username}.");
           context.Result = new GrantValidationResult(TokenRequestErrors.InvalidRequest, $"No user {username} found for for {company}.");
           return Task.FromResult(0);
       }

       _logger.LogInformation("Resource owner password validation succeeded.");
        context.Result = user.Password == password ? new GrantValidationResult(context.UserName, GrantType.ResourceOwnerPassword, new [] { new Claim("company", company) }) : new GrantValidationResult(TokenRequestErrors.InvalidRequest, "Invalid username or password.");

        return Task.FromResult(0);
  }

我使用 Postman 通过点击令牌端点来验证我的结果。首先,我在我的 IdentityServer4 实现中配置了一个客户端:

new Client {
    AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
    AllowedScopes = {
        IdentityServerConstants.StandardScopes.OpenId,
        IdentityServerConstants.StandardScopes.Profile,
        IdentityServerConstants.StandardScopes.Email
    },
    ClientId = "test.client",
    ClientName = "Test Client",
    ClientSecrets = new List<Secret>
    {
        new Secret("secret".Sha256())
    }
}

在 Postman 中,我选择了 Basic Auth 作为授权类型。 用户名ClientId密码Client Secret.我将动词设置为 POST 并指定了一个 "x-www-form-urlencoded" 正文。端点需要用户名、密码、grant_type 和范围的最小值。我添加了公司 key/value 参数:

我有类似的实施要求,但使用 acr_values header 和 tenant:company1 作为公司标识符会不会更令人不满?