Я боролся с этой проблемой в течение нескольких часов и, наконец, собрал решение. Это article была большая помощь, но подвести итоги и поделиться реализации:
Для того, чтобы получить требования, назначенные пользователю и присоединить их к маркеру доступа, необходимо реализовать два интерфейса на сервере идентичности: IResourceOwnerPasswordValidator
и IProfileService
. Ниже перечислены мои реализации двух классов и черновики, но они работают.
** Обязательно получите последнюю версию IdentityServer4 - 1.0.2 в настоящее время.
public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
{
private readonly UserManager<ApplicationUser> _userManager;
public ResourceOwnerPasswordValidator(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
public Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{
var userTask = _userManager.FindByNameAsync(context.UserName);
var user = userTask.Result;
context.Result = new GrantValidationResult(user.Id, "password", null, "local", null);
return Task.FromResult(context.Result);
}
}
и
public class AspNetIdentityProfileService : IProfileService
{
private readonly UserManager<ApplicationUser> _userManager;
public AspNetIdentityProfileService(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var subject = context.Subject;
if (subject == null) throw new ArgumentNullException(nameof(context.Subject));
var subjectId = subject.GetSubjectId();
var user = await _userManager.FindByIdAsync(subjectId);
if (user == null)
throw new ArgumentException("Invalid subject identifier");
var claims = await GetClaimsFromUser(user);
var siteIdClaim = claims.SingleOrDefault(x => x.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress");
context.IssuedClaims.Add(new Claim(JwtClaimTypes.Email, user.Email));
context.IssuedClaims.Add(new Claim("siteid", siteIdClaim.Value));
context.IssuedClaims.Add(new Claim(JwtClaimTypes.Role, "User"));
var roleClaims = claims.Where(x => x.Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/role");
foreach (var roleClaim in roleClaims)
{
context.IssuedClaims.Add(new Claim(JwtClaimTypes.Role, roleClaim.Value));
}
}
public async Task IsActiveAsync(IsActiveContext context)
{
var subject = context.Subject;
if (subject == null) throw new ArgumentNullException(nameof(context.Subject));
var subjectId = subject.GetSubjectId();
var user = await _userManager.FindByIdAsync(subjectId);
context.IsActive = false;
if (user != null)
{
if (_userManager.SupportsUserSecurityStamp)
{
var security_stamp = subject.Claims.Where(c => c.Type == "security_stamp").Select(c => c.Value).SingleOrDefault();
if (security_stamp != null)
{
var db_security_stamp = await _userManager.GetSecurityStampAsync(user);
if (db_security_stamp != security_stamp)
return;
}
}
context.IsActive =
!user.LockoutEnabled ||
!user.LockoutEnd.HasValue ||
user.LockoutEnd <= DateTime.Now;
}
}
private async Task<IEnumerable<Claim>> GetClaimsFromUser(ApplicationUser user)
{
var claims = new List<Claim>
{
new Claim(JwtClaimTypes.Subject, user.Id),
new Claim(JwtClaimTypes.PreferredUserName, user.UserName)
};
if (_userManager.SupportsUserEmail)
{
claims.AddRange(new[]
{
new Claim(JwtClaimTypes.Email, user.Email),
new Claim(JwtClaimTypes.EmailVerified, user.EmailConfirmed ? "true" : "false", ClaimValueTypes.Boolean)
});
}
if (_userManager.SupportsUserPhoneNumber && !string.IsNullOrWhiteSpace(user.PhoneNumber))
{
claims.AddRange(new[]
{
new Claim(JwtClaimTypes.PhoneNumber, user.PhoneNumber),
new Claim(JwtClaimTypes.PhoneNumberVerified, user.PhoneNumberConfirmed ? "true" : "false", ClaimValueTypes.Boolean)
});
}
if (_userManager.SupportsUserClaim)
{
claims.AddRange(await _userManager.GetClaimsAsync(user));
}
if (_userManager.SupportsUserRole)
{
var roles = await _userManager.GetRolesAsync(user);
claims.AddRange(roles.Select(role => new Claim(JwtClaimTypes.Role, role)));
}
return claims;
}
}
После того, как у вас есть те, что они должны быть добавлены к вашим услугам в startup.cs:
services.AddTransient<IResourceOwnerPasswordValidator, ResourceOwnerPasswordValidator>();
services.AddTransient<IProfileService, AspNetIdentityProfileService>();
Вот быстрый взгляд на моей конфигурации :
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId()
};
}
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource
{
Name = "api1",
Description = "My Api",
Scopes =
{
new Scope()
{
Name = "api1",
DisplayName = "Full access to Api"
}
}
}
};
}
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client
{
ClientId = "apiClient",
ClientName = "Api Angular2 Client",
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
AlwaysSendClientClaims = true,
AlwaysIncludeUserClaimsInIdToken = true,
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedScopes =
{
"api1"
}
}
};
}
После этого, вызов к серверу идентичности от клиента:
var discoTask = DiscoveryClient.GetAsync("http://localhost:5000");
var disco = discoTask.Result;
var tokenClient = new TokenClient(disco.TokenEndpoint, "apiClient", "secret");
var tokenResponseTask = tokenClient.RequestResourceOwnerPasswordAsync("[email protected]", "my-password", "api1");
var tokenResponse = tokenResponseTask.Result;
var accessToken = tokenResponse.AccessToken;
if (tokenResponse.IsError)
{
Console.WriteLine(tokenResponse.Error);
return;
}
Осмотреть маркер на jwt.io и увидеть результаты ...