Actualización: IdentityServer 4 ha cambiado y reemplazado IUserService con IResourceOwnerPasswordValidator e IProfileService
Usé mi UserRepository para obtener todos los datos del usuario de la base de datos. Esto se inyecta (DI) en los constructores y se define en Startup.cs
. También creé las siguientes clases para el servidor de identidad (que también se inyecta):
Primero defina ResourceOwnerPasswordValidator.cs
:
public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
{
private readonly IUserRepository _userRepository;
public ResourceOwnerPasswordValidator(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{
try
{
var user = await _userRepository.FindAsync(context.UserName);
if (user != null)
{
if (user.Password == context.Password) {
context.Result = new GrantValidationResult(
subject: user.UserId.ToString(),
authenticationMethod: "custom",
claims: GetUserClaims(user));
return;
}
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "Incorrect password");
return;
}
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "User does not exist.");
return;
}
catch (Exception ex)
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "Invalid username or password");
}
}
public static Claim[] GetUserClaims(User user)
{
return new Claim[]
{
new Claim("user_id", user.UserId.ToString() ?? ""),
new Claim(JwtClaimTypes.Name, (!string.IsNullOrEmpty(user.Firstname) && !string.IsNullOrEmpty(user.Lastname)) ? (user.Firstname + " " + user.Lastname) : ""),
new Claim(JwtClaimTypes.GivenName, user.Firstname ?? ""),
new Claim(JwtClaimTypes.FamilyName, user.Lastname ?? ""),
new Claim(JwtClaimTypes.Email, user.Email ?? ""),
new Claim("some_claim_you_want_to_see", user.Some_Data_From_User ?? ""),
new Claim(JwtClaimTypes.Role, user.Role)
};
}
Y ProfileService.cs
:
public class ProfileService : IProfileService
{
private readonly IUserRepository _userRepository;
public ProfileService(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
try
{
if (!string.IsNullOrEmpty(context.Subject.Identity.Name))
{
var user = await _userRepository.FindAsync(context.Subject.Identity.Name);
if (user != null)
{
var claims = GetUserClaims(user);
context.IssuedClaims = claims.Where(x => context.RequestedClaimTypes.Contains(x.Type)).ToList();
}
}
else
{
var userId = context.Subject.Claims.FirstOrDefault(x => x.Type == "sub");
if (!string.IsNullOrEmpty(userId?.Value) && long.Parse(userId.Value) > 0)
{
var user = await _userRepository.FindAsync(long.Parse(userId.Value));
if (user != null)
{
var claims = ResourceOwnerPasswordValidator.GetUserClaims(user);
context.IssuedClaims = claims.Where(x => context.RequestedClaimTypes.Contains(x.Type)).ToList();
}
}
}
}
catch (Exception ex)
{
}
}
public async Task IsActiveAsync(IsActiveContext context)
{
try
{
var userId = context.Subject.Claims.FirstOrDefault(x => x.Type == "user_id");
if (!string.IsNullOrEmpty(userId?.Value) && long.Parse(userId.Value) > 0)
{
var user = await _userRepository.FindAsync(long.Parse(userId.Value));
if (user != null)
{
if (user.IsActive)
{
context.IsActive = user.IsActive;
}
}
}
}
catch (Exception ex)
{
}
}
}
Luego Startup.cs
, hice lo siguiente:
public void ConfigureServices(IServiceCollection services)
{
var cert = new X509Certificate2(Path.Combine(_environment.ContentRootPath, "idsrv4test.pfx"), "your_cert_password");
services.AddScoped(_ => new YourDbContext(Configuration.GetConnectionString("DefaultConnection")));
services.AddScoped<IUserRepository, UserRepository>();
services.AddIdentityServer()
.AddSigningCredential(cert)
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients())
.AddProfileService<ProfileService>();
services.AddTransient<IResourceOwnerPasswordValidator, ResourceOwnerPasswordValidator>();
services.AddTransient<IProfileService, ProfileService>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseIdentityServer();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
IdentityServerAuthenticationOptions identityServerValidationOptions = new IdentityServerAuthenticationOptions
{
Authority = "http://localhost:50000/",
ApiSecret = "secret",
ApiName = "my.api.resource",
AutomaticAuthenticate = true,
SupportedTokens = SupportedTokens.Both,
AutomaticChallenge = true,
RequireHttpsMetadata = false
};
app.UseIdentityServerAuthentication(identityServerValidationOptions);
}
También necesitará Config.cs
cuál define sus clientes, api y recursos. Puede encontrar un ejemplo aquí: https://github.com/IdentityServer/IdentityServer4.Demo/blob/master/src/IdentityServer4Demo/Config.cs
Ahora debería poder llamar a IdentityServer / connect / token
Para obtener más información, consulte la documentación: https://media.readthedocs.org/pdf/identityserver4/release/identityserver4.pdf
Respuesta anterior (esto ya no funciona para IdentityServer4 más nuevo)
Es bastante simple una vez que comprendes el flujo de las cosas.
Configure su IdentityService así (en Startup.cs - ConfigureServices()
):
var builder = services.AddIdentityServer(options =>
{
options.SigningCertificate = cert;
});
builder.AddInMemoryClients(Clients.Get());
builder.AddInMemoryScopes(Scopes.Get());
builder.Services.AddTransient<IUserService, UserService>();
services.AddTransient<IUserRepository, UserRepository>();
Luego configure su UserService
public class UserService : IUserService
{
private IUserRepository _userRepository;
public UserService(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public Task AuthenticateLocalAsync(LocalAuthenticationContext context)
{
var user = _userRepository.Find(context.UserName);
if (user.Password == context.Password)
{
context.AuthenticateResult = new AuthenticateResult(
user.UserId.ToString(),
user.Email,
new Claim[]
{
new Claim(Constants.ClaimTypes.Name, user.Firstname + " " + user.Surname),
new Claim(Constants.ClaimTypes.Email, user.Email),
new Claim(Constants.ClaimTypes.Role, user.Role.ToString()),
new Claim("company", user.Company)
}
);
}
return Task.FromResult(0);
}
public Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var user = _userRepository.Find(context.Subject.Identity.Name);
if (user != null)
{
var claims = new Claim[]
{
new Claim(Constants.ClaimTypes.Name, user.Firstname + " " + user.Surname),
new Claim(Constants.ClaimTypes.Email, user.Email),
new Claim(Constants.ClaimTypes.Role, user.Role.ToString(), ClaimValueTypes.Integer),
new Claim("company", user.Company)
};
context.IssuedClaims = claims.Where(x => context.RequestedClaimTypes.Contains(x.Type));
}
return Task.FromResult(0);
}
public Task IsActiveAsync(IsActiveContext context)
{
var user = _userRepository.Find(context.Subject.Identity.Name);
return Task.FromResult(user != null);
}
}
Básicamente al inyectar UserService
en builder
(de tipo IdentityServerBuilder
) Services
, le permite llamar al UserService en auth.
Espero que esto ayude a otros, ya que me tomó un par de horas poner esto en marcha.
IUserService
en IdSvr4 (para ASP.NET Core 1.0) ya no existe. Ha sido reemplazado por dos interfaces / serviciosIProfileService
yIResourceOwnerPasswordValidator
.context.IssuedClaims = context.Subject.Claims.ToList();
en GetProfileData, solo depende de si necesita ocultar algunas reclamaciones al público o si necesita hacer una lógica intermedia al ver los datos del perfil.En IdentityServer4.
IUserService
ya no está disponible, ahora debe usarIResourceOwnerPasswordValidator
para realizar la autenticación y usarIProfileService
para obtener las reclamaciones.En mi escenario, uso el tipo de concesión del propietario del recurso, y todo lo que necesito es obtener los reclamos de los usuarios para realizar una autorización basada en roles para mis API web de acuerdo con el nombre de usuario y la contraseña. Y asumí que el tema es único para cada usuario.
He publicado mis códigos a continuación y puede funcionar correctamente; ¿Alguien podría decirme si hay algún problema con mis códigos?
Registre estos dos servicios en startup.cs.
public void ConfigureServices(IServiceCollection services) { var builder = services.AddIdentityServer(); builder.AddInMemoryClients(Clients.Get()); builder.AddInMemoryScopes(Scopes.Get()); builder.Services.AddTransient<IResourceOwnerPasswordValidator, ResourceOwnerPasswordValidator>(); builder.Services.AddTransient<IProfileService, ProfileService>(); }
Implementa la
IResourceOwnerPasswordValidator
interfaz.public class ResourceOwnerPasswordValidator: IResourceOwnerPasswordValidator { public Task<customgrantvalidationresult> ValidateAsync(string userName, string password, ValidatedTokenRequest request) { // Check The UserName And Password In Database, Return The Subject If Correct, Return Null Otherwise // subject = ...... if (subject == null) { var result = new CustomGrantValidationResult("Username Or Password Incorrect"); return Task.FromResult(result); } else { var result = new CustomGrantValidationResult(subject, "password"); return Task.FromResult(result); } } }
Implementa la
ProfileService
interfaz.public class ProfileService : IProfileService { public Task GetProfileDataAsync(ProfileDataRequestContext context) { string subject = context.Subject.Claims.ToList().Find(s => s.Type == "sub").Value; try { // Get Claims From Database, And Use Subject To Find The Related Claims, As A Subject Is An Unique Identity Of User //List<string> claimStringList = ...... if (claimStringList == null) { return Task.FromResult(0); } else { List<Claim> claimList = new List<Claim>(); for (int i = 0; i < claimStringList.Count; i++) { claimList.Add(new Claim("role", claimStringList[i])); } context.IssuedClaims = claimList.Where(x => context.RequestedClaimTypes.Contains(x.Type)); return Task.FromResult(0); } } catch { return Task.FromResult(0); } } public Task IsActiveAsync(IsActiveContext context) { return Task.FromResult(0); } }
fuente
"IdentityServer4": "1.3.1"
En IdentityServer4 1.0.0-rc5 ni IUserService ni CustomGrantValidationResult están disponibles.
Ahora, en lugar de devolver un CustomGrantValidationResult, deberá establecer el context.Result.
public class ResourceOwnerPasswordValidator: IResourceOwnerPasswordValidator { private MyUserManager _myUserManager { get; set; } public ResourceOwnerPasswordValidator() { _myUserManager = new MyUserManager(); } public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context) { var user = await _myUserManager.FindByNameAsync(context.UserName); if (user != null && await _myUserManager.CheckPasswordAsync(user,context.Password)) { context.Result = new GrantValidationResult( subject: "2", authenticationMethod: "custom", claims: someClaimsList); } else { context.Result = new GrantValidationResult( TokenRequestErrors.InvalidGrant, "invalid custom credential"); } return; }
Validación de la contraseña del propietario del recurso
fuente