IdentityServer4 密码模式 接入现有用户数据表
首先定义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 { //从你的数据库查询用户信息,或者添加其他的与角色相关的权限验证 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); //... }
参考文章:
- https://stackoverflow.com/questions/35304038/identityserver4-register-userservice-and-get-users-from-database-in-asp-net-core
在各种客户端中使用的样例:
浙公网安备 33010602011771号