2016-02-09 32 views
39

我已經搜索瞭如何在asp.net核心中註冊與IdentityServer4的UserService,但我似乎找不到正確的方式來做到這一點。IdentityServer4註冊UserService並從asp.net核心中的數據庫獲取用戶

這是註冊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核心

謝謝!

回答

32

更新 - IdentityServer 4已經改變,並與IResourceOwnerPasswordValidator更換IUserService IProfileService

我用我的UserRepository獲取數據庫中的所有用戶數據。這是注入(DI)到構造函數中,並在Startup.cs中定義。我還創建了身份服務器下面的類(也注入):

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

    //... 
} 

Yo你也需要Config.cs它定義你的客戶端,API和資源。您可以在這裏找到一個例子:https://github.com/IdentityServer/IdentityServer4.Demo/blob/master/src/IdentityServer4Demo/Config.cs

你現在應該能夠調用IdentityServer /連接/令牌

enter image description here

對於任何進一步的信息,請查看文檔:https://media.readthedocs.org/pdf/identityserver4/release/identityserver4.pdf


舊的回答(這樣做不是工作更新的IdentityServer4了)

一旦你理解了事物的流動,它就非常簡單。

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

基本上通過注入UserServicebuilder(的IdentityServerBuilder型)Services,允許它在auth上調用UserService。

我希望這可以幫助別人,因爲花了我幾個小時才能完成這個任務。

+7

嗯,從我所看到的,'IUserService'上IdSvr4(用於ASP.NET 1.0的核心)不再存在。它已被兩個接口/服務「IProfileService」和「IResourceOwnerPasswordValidator」取代。 –

+2

是的 - 前進 - 他們將分裂。另外的擔憂。 – leastprivilege

+1

爲什麼選擇的答案,如果它甚至不適用於身份識別服務器4 – Sinaesthetic

46

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

我按照這個答案,但我收到以下錯誤:「其他信息:沒有指定授予的存儲機制。使用'AddInMemoryStores'擴展方法註冊開發版本」。 我使用「services.AddIdentityServer」來創建構建器,IdentitiServer4的版本是1.0.0-rc1-update2。 – fra

+0

值得指出的是,如果您想要控制「子」聲明,那麼您必須在流水線之前進行一些定製。 –

+0

即使我提供兩種服務的實現,它仍然存在相同的錯誤! – Coding

4

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

現在不是返回CustomGrantValidationResult你將需要設置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; 

    } 

Resource Owner Password Validation

相關問題