IdentityServer4 UserService'e kaydolur ve kullanıcıları asp.net çekirdeğindeki veritabanından alır


84

UserServiceAsp.net çekirdeğinde IdentityServer4'e nasıl kaydolacağımı her yerde araştırdım , ancak bunu yapmanın doğru yolunu bulamıyorum.

Bu, burada bulunan InMemoryUsers'ı kaydetme kodudur , ancak kullanıcılara MSSQL DB'mden , örnekte tanımlanan statik olmayan kullanıcılardan erişmek istiyorum.

var builder = services.AddIdentityServer(options =>
{
    options.SigningCertificate = cert;
});

builder.AddInMemoryClients(Clients.Get());
builder.AddInMemoryScopes(Scopes.Get());
builder.AddInMemoryUsers(Users.Get());

Ondan sonra baktım bu içindir ki IdentityServer3 .

var factory = new IdentityServerServiceFactory()
                .UseInMemoryClients(Clients.Get())
                .UseInMemoryScopes(Scopes.Get());

var userService = new UserService();
factory.UserService = new Registration<IUserService>(resolver => userService);

Çevrimiçi okumaktan, UserService'i kaydetmek için DI sistemini kullanmam gerekiyor gibi görünüyor, ancak IdentityServer'a nasıl bağlandığından emin değilim, örn.

services.AddScoped<IUserService, UserService>();

Yani sorum şu:

Kendimi UserServiceoluşturucuya (IdentityServer4 Kullanıcıları) nasıl bağlarım ? Ve var olan db kullanıcılarıma erişmek ve kimliklerini doğrulamak için veritabanımı nasıl arayabilirim UserService(db'ye bağlanmak için depoları kullanıyorum)?

Dikkate alarak bu sahiptir çalışmak asp.net çekirdek .

Teşekkürler!

Yanıtlar:


117

Güncelleme - IdentityServer 4, IUserService'i IResourceOwnerPasswordValidator ve IProfileService ile değiştirdi ve değiştirdi

Veritabanından tüm kullanıcı verilerini almak için UserRepository'mi kullandım . Bu, yapıcılara enjekte edilir (DI) ve içinde tanımlanır Startup.cs. Ayrıca kimlik sunucusu için aşağıdaki sınıfları da oluşturdum (bu da enjekte edildi):

Önce şunu tanımlayın ResourceOwnerPasswordValidator.cs:

public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
{
    //repository to get user from db
    private readonly IUserRepository _userRepository;

    public ResourceOwnerPasswordValidator(IUserRepository userRepository)
    {
        _userRepository = userRepository; //DI
    }

    //this is used to validate your user account with provided grant at /connect/token
    public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
    {
        try
        {
            //get your user model from db (by username - in my case its email)
            var user = await _userRepository.FindAsync(context.UserName);
            if (user != null)
            {
                //check if password match - remember to hash password if stored as hash in db
                if (user.Password == context.Password) {
                    //set the result
                    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");
        }
    }

    //build claims array from user data
    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 ?? ""),

            //roles
            new Claim(JwtClaimTypes.Role, user.Role)
        };
}

Ve ProfileService.cs:

public class ProfileService : IProfileService
{
    //services
    private readonly IUserRepository _userRepository;

    public ProfileService(IUserRepository userRepository)
    {
        _userRepository = userRepository;
    }

    //Get user profile date in terms of claims when calling /connect/userinfo
    public async Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        try
        {
            //depending on the scope accessing the user data.
            if (!string.IsNullOrEmpty(context.Subject.Identity.Name))
            {
                //get user from db (in my case this is by email)
                var user = await _userRepository.FindAsync(context.Subject.Identity.Name);

                if (user != null)
                {
                    var claims = GetUserClaims(user);

                    //set issued claims to return
                    context.IssuedClaims = claims.Where(x => context.RequestedClaimTypes.Contains(x.Type)).ToList();
                }
            }
            else
            {
                //get subject from context (this was set ResourceOwnerPasswordValidator.ValidateAsync),
                //where and subject was set to my user id.
                var userId = context.Subject.Claims.FirstOrDefault(x => x.Type == "sub");

                if (!string.IsNullOrEmpty(userId?.Value) && long.Parse(userId.Value) > 0)
                {
                    //get user from db (find user by user id)
                    var user = await _userRepository.FindAsync(long.Parse(userId.Value));

                    // issue the claims for the user
                    if (user != null)
                    {
                        var claims = ResourceOwnerPasswordValidator.GetUserClaims(user);

                        context.IssuedClaims = claims.Where(x => context.RequestedClaimTypes.Contains(x.Type)).ToList();
                    }
                }
            }
        }
        catch (Exception ex)
        {
            //log your error
        }
    }

    //check if user account is active.
    public async Task IsActiveAsync(IsActiveContext context)
    {
        try
        {
            //get subject from context (set in ResourceOwnerPasswordValidator.ValidateAsync),
            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)
        {
            //handle error logging
        }
    }
}

Sonra Startup.csşunları yaptım:

public void ConfigureServices(IServiceCollection services)
{
    //...

    //identity server 4 cert
    var cert = new X509Certificate2(Path.Combine(_environment.ContentRootPath, "idsrv4test.pfx"), "your_cert_password");

    //DI DBContext inject connection string
    services.AddScoped(_ => new YourDbContext(Configuration.GetConnectionString("DefaultConnection")));

    //my user repository
    services.AddScoped<IUserRepository, UserRepository>();

    //add identity server 4
    services.AddIdentityServer()
        .AddSigningCredential(cert)
        .AddInMemoryIdentityResources(Config.GetIdentityResources()) //check below
        .AddInMemoryApiResources(Config.GetApiResources())
        .AddInMemoryClients(Config.GetClients())
        .AddProfileService<ProfileService>();

    //Inject the classes we just created
    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
    {
        //move host url into appsettings.json
        Authority = "http://localhost:50000/",
        ApiSecret = "secret",
        ApiName = "my.api.resource",
        AutomaticAuthenticate = true,
        SupportedTokens = SupportedTokens.Both,

        // required if you want to return a 403 and not a 401 for forbidden responses
        AutomaticChallenge = true,

        //change this to true for SLL
        RequireHttpsMetadata = false
    };

    app.UseIdentityServerAuthentication(identityServerValidationOptions);

    //...
}

Ayrıca Config.cs, müşterilerinizi, API'lerinizi ve kaynaklarınızı tanımlayanlara da ihtiyacınız olacak . Burada bir örnek bulabilirsiniz: https://github.com/IdentityServer/IdentityServer4.Demo/blob/master/src/IdentityServer4Demo/Config.cs

Artık IdentityServer / connect / token çağırabilmelisiniz

görüntü açıklamasını buraya girin

Daha fazla bilgi için lütfen belgeleri kontrol edin: https://media.readthedocs.org/pdf/identityserver4/release/identityserver4.pdf


Eski yanıt (bu mu değil artık yeni IdentityServer4 için çalışmak)

İşlerin akışını anladıktan sonra oldukça basit.

IdentityService'inizi şu şekilde yapılandırın (Startup.cs - ConfigureServices()):

var builder = services.AddIdentityServer(options =>
{
    options.SigningCertificate = cert;
});

builder.AddInMemoryClients(Clients.Get());
builder.AddInMemoryScopes(Scopes.Get());

//** this piece of code DI's the UserService into IdentityServer **
builder.Services.AddTransient<IUserService, UserService>();

//for clarity of the next piece of code
services.AddTransient<IUserRepository, UserRepository>();

Ardından Kullanıcı Hizmetinizi kurun

public class UserService : IUserService
{
    //DI the repository from Startup.cs - see previous code block
    private IUserRepository _userRepository;

    public UserService(IUserRepository userRepository)
    {
        _userRepository = userRepository;
    }

    public Task AuthenticateLocalAsync(LocalAuthenticationContext context)
    {
        var user = _userRepository.Find(context.UserName);

        //check if passwords match against user column 
        //My password was hashed, 
        //so I had to hash it with the saved salt first and then compare.
        if (user.Password == context.Password)
        {
            context.AuthenticateResult = new AuthenticateResult(
                user.UserId.ToString(),
                user.Email,

                //I set up some claims 
                new Claim[]
                {
                    //Firstname and Surname are DB columns mapped to User object (from table [User])
                    new Claim(Constants.ClaimTypes.Name, user.Firstname + " " + user.Surname),
                    new Claim(Constants.ClaimTypes.Email, user.Email),
                    new Claim(Constants.ClaimTypes.Role, user.Role.ToString()),
                    //custom claim
                    new Claim("company", user.Company)
                }
            );
        }

        return Task.FromResult(0);
    }

    public Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        //find method in my repository to check my user email
        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);
    }
}

Temel UserServiceolarak builder(türünün IdentityServerBuilder) içine enjekte ederek Services, kimlik doğrulamasında UserService'i çağırmasına izin verir.

Umarım bu, başkalarına yardımcı olur, çünkü bunu yapmam birkaç saat sürdü.


10
Hmmm, görebildiğim IUserServicekadarıyla, IdSvr4 üzerinde (ASP.NET Core 1.0 için) artık mevcut değil. İki arayüz / hizmet IProfileServiceile değiştirildi ve IResourceOwnerPasswordValidator.
Frank Fajardo

3
Evet - ileriye doğru - bölünecekler. Ayrı endişeler.
lessprivilege

3
@Sinaesthetic - Bunun için çok üzgünüz, kimlik sunucusu4 bu yanıt gönderildikten sonra güncellendi ve artık IUserService kullanmıyor. Cevabımı güncelledim, umarım bu yardımcı olur.
Nick De Beer

3
@Uros - Yalnızca context.IssuedClaims = context.Subject.Claims.ToList();GetProfileData'da arama yapabilmelisiniz , sadece bazı iddiaları genelden gizlemeniz veya profil verilerini görüntülerken bazı aracı mantık yapmanız gerekip gerekmediğine bağlıdır.
Nick De Beer

3
Bunun .net core 2 için güncellenmesi gerekiyor mu? Hem IProfileServiece hem de IResourceOwnerPasswordValidator uyguladım ancak hiçbiri kimlik sunucusu tarafından çağrılmadı.
stt106

66

IdentityServer4'te. IUserServiceartık mevcut değil, şimdi IResourceOwnerPasswordValidatorkimlik doğrulamasını yapmak IProfileServiceve talepleri almak için kullanmak zorundasınız .

Senaryomda, kaynak sahibi hibe türünü kullanıyorum ve tek ihtiyacım olan, kullanıcıların kullanıcı adı ve şifreye göre Web API'lerim için rol tabanlı yetkilendirme yapma iddialarını almaktır. Ve konunun her kullanıcı için benzersiz olduğunu varsaydım.

Kodlarımı aşağıya gönderdim ve düzgün çalışabilir; biri bana kodlarımla ilgili herhangi bir sorun olduğunu söyleyebilir mi?

Bu iki hizmeti startup.cs dosyasında kaydedin.

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>();
}

Uygulamak IResourceOwnerPasswordValidatorarayüz.

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);
        }
    }
}

Uygulamak ProfileServicearayüz.

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);
    }
}

Bu yanıtı izledim, ancak şu hatayı alıyorum: "Ek bilgi: Hibeler için depolama mekanizması belirtilmedi. Bir geliştirme sürümünü kaydetmek için 'AddInMemoryStores' uzantı yöntemini kullanın". Oluşturucuyu oluşturmak için "services.AddIdentityServer" kullanıyorum, IdentitiServer4 sürümü 1.0.0-rc1-update2.
fra

"Alt" talebin kontrolünü ele geçirmek istiyorsanız, bundan daha önce boru hattında bazı özelleştirme yapmanız gerektiğine dikkat çekmek gerekir.
Ben Collins

Her iki hizmet için uygulama sağlasam bile aynı hata devam ediyor!
Hüseyin Salman

@EternityWYH şuna bir göz atabilir misin [ stackoverflow.com/questions/40797993/…
Hüseyin Salman

Cevabınız için teşekkürler, benim için IResourceOwnerPasswordValidator ve IProfileService'i uygulamak yeterliydi"IdentityServer4": "1.3.1"
Ilya Chumakov

10

IdentityServer4 1.0.0-rc5'te ne IUserService ne de CustomGrantValidationResult kullanılamaz.

Şimdi bir CustomGrantValidationResult döndürmek yerine, bağlamı ayarlamanız gerekecek.

 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;

   }

Kaynak Sahibi Parola Doğrulaması

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.