首页 文章

IdentityServer4注册UserService并从asp.net核心的数据库中获取用户

提问于
浏览
50

我已经搜遍了如何在asp.net核心中使用IdentityServer4注册 UserService ,但我似乎找不到正确的方法来做到这一点 .

这是注册InMemoryUsers找到here的代码,但我想从我的MSSQL DB访问用户,而不是样本中定义的静态用户 .

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

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

那么我看了this这是 IdentityServer3 .

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

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

从在线阅读看起来我需要使用DI系统注册UserService,但我不确定它如何绑定到IdentityServer,例如 .

services.AddScoped<IUserService, UserService>();

所以我的问题是:

如何将 UserService 绑定到构建器(IdentityServer4用户)?我将如何调用我的数据库来访问和验证 UserService 中的现有数据库用户(我使用存储库连接到数据库)?

考虑到这一点必须与 asp.net core 一起使用 .

谢谢!

3 回答

  • 58

    在IdentityServer4中 . IUserService 不再可用,现在您必须使用 IResourceOwnerPasswordValidator 进行身份验证并使用 IProfileService 来获取声明 .

    在我的场景中,我使用资源所有者授权类型,我所需要的只是根据用户名和密码获取用户声称为我的Web API执行基于角色的授权 . 我认为主题对每个用户都是独一无二的 .

    我在下面发布了我的代码,它可以正常工作;有人能告诉我,我的代码有什么问题吗?

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

    实现 IResourceOwnerPasswordValidator 接口 .

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

    实现 ProfileService 接口 .

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

    Update - IdentityServer 4 has changed and replaced IUserService with IResourceOwnerPasswordValidator and IProfileService

    我使用我的UserRepository从数据库中获取所有用户数据 . 这被注入(DI)到构造函数中,并在_921228中定义 . 我还为身份服务器创建了以下类(也是注入的):

    首先定义 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)
            };
    }
    

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

    然后在 Startup.cs 我做了以下事情:

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

    您还需要 Config.cs 来定义您的客户,api和资源 . 你可以在这里找到一个例子:https://github.com/IdentityServer/IdentityServer4.Demo/blob/master/src/IdentityServer4Demo/Config.cs

    您现在应该能够调用IdentityServer / connect / token

    enter image description here

    有关更多信息,请查看文档:https://media.readthedocs.org/pdf/identityserver4/release/identityserver4.pdf


    Old answer (对于较新的IdentityServer4,这会 not 工作了)

    一旦你理解了事物的流动,它就非常简单 .

    像这样配置IdentityService(在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>();
    

    然后设置您的UserService

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

    基本上通过将 UserService 注入 builder (类型为 IdentityServerBuilderServices ,允许它在auth上调用UserService .

    我希望这能帮助别人,因为我花了几个小时才能实现这一目标 .

  • 6

    在IdentityServer4 1.0.0-rc5中,IUserService和CustomGrantValidationResult都不可用 .

    现在,您需要设置context.Result,而不是返回CustomGrantValidationResult .

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

    Resource Owner Password Validation

相关问题