我需要适应 IdentityServer4 的遗留系统需要三个信息来验证用户:公司标识符、用户名和密码。
我们的系统有一个主数据库,其中包含一个公司表,其中包含与公司特定数据库(租户)的连接字符串。每个租户数据库都有一个用户表,其中包含用户配置文件信息,包括用户名和密码。
使用 Scott Brady 的回答,我能够实现适用于我们情况的 IResourceOwnerPasswordValidator,但并非没有深入研究 IdentityModel.TokenClient 源代码。我发现令牌端点 (token_endpoint: "http:///connect/token") 采用 URL 编码形式并序列化键/值对,并通过 Request.Raw 的属性使它们在 IResourceOwnerPasswordValidator.ValidateAsync 中可用ResourceOwnerPasswordValidationContext 参数。
这是我的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 中,我选择了基本身份验证作为授权类型。 用户名是ClientId,密码是Client Secret。我将动词设置为 POST 并指定了“x-www-form-urlencoded”正文。端点需要用户名、密码、授权类型和范围的最小值。我添加了一个公司键/值参数: