4

Wie können weitere Ansprüche hinzugefügt werden, die im Token enthalten sein sollen?Hinzufügen weiterer Ansprüche, die im access_token mit ASP.Net Identity mit IdentityServer4 enthalten sein sollen

Sobald die API das Bearer-Token empfängt, wird das User.Identity-Objekt mit den folgenden Ansprüchen gefüllt.

[ 
    { 
    "key": "nbf", 
    "value": "1484614344" 
    }, 
    { 
    "key": "exp", 
    "value": "1484615244" 
    }, 
    { 
    "key": "iss", 
    "value": "http://localhost:85" 
    }, 
    { 
    "key": "aud", 
    "value": "http://localhost:85/resources" 
    }, 
    { 
    "key": "aud", 
    "value": "WebAPI" 
    }, 
    { 
    "key": "client_id", 
    "value": "MyClient" 
    }, 
    { 
    "key": "sub", 
    "value": "d74c815a-7ed3-4671-b4e4-faceb0854bf6" 
    }, 
    { 
    "key": "auth_time", 
    "value": "1484611732" 
    }, 
    { 
    "key": "idp", 
    "value": "local" 
    }, 
    { 
    "key": "role", 
    "value": "AccountsManager" 
    }, 
    { 
    "key": "scope", 
    "value": "openid" 
    }, 
    { 
    "key": "scope", 
    "value": "profile" 
    }, 
    { 
    "key": "scope", 
    "value": "roles" 
    }, 
    { 
    "key": "scope", 
    "value": "WebAPI" 
    }, 
    { 
    "key": "scope", 
    "value": "offline_access" 
    }, 
    { 
    "key": "amr", 
    "value": "pwd" 
    } 
] 

Ich möchte weitere Ansprüche wie username, email, legacySystemUserId etc. Diese Felder sind bereits in der AspNetUsers Tabelle (und wiederholt nicht in AspNetUserClaims Tabelle vorhanden sind) und sind in ASP .Net Core-Anwendung in meinem ApplicationUser Objekt zur Verfügung.

Ich möchte, dass sie in das Zugriffstoken aufgenommen werden, das nach der Authentifizierung mit Benutzername und Kennwort zurückgegeben wird. Ich möchte dasselbe in meiner WebAPI-Anwendung verwenden, die keinen Zugriff auf die Identity-Server-Datenbank hat und deren eigene Datenbank Daten basierend auf der E-Mail-Adresse des Benutzers und nicht der UserId speichert (was eine GUID ist, die in ASP.NET Identity generiert und empfangen wird Unteranspruch).

Antwort

11

Ich hatte das gleiche Problem seit Stunden bekämpft und schließlich die Lösung zusammengesetzt. Diese article war eine große Hilfe, aber zusammenzufassen und meine Implementierung zu teilen:

Um die Ansprüche an den Benutzer zugewiesen und an das Access-Token anhängen, müssen Sie zwei Schnittstellen auf dem Identitätsserver implementieren: IResourceOwnerPasswordValidator und IProfileService. Das Folgende sind meine Implementierungen der beiden Klassen und sind grobe Entwürfe, aber sie funktionieren.

** Achten Sie darauf, die neueste Version von IdentityServer4 - 1.0.2 zu diesem Zeitpunkt zu erhalten.

public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator 
{ 
    private readonly UserManager<ApplicationUser> _userManager; 

    public ResourceOwnerPasswordValidator(UserManager<ApplicationUser> userManager) 
    { 
     _userManager = userManager; 
    } 

    public Task ValidateAsync(ResourceOwnerPasswordValidationContext context) 
    { 
     var userTask = _userManager.FindByNameAsync(context.UserName); 
     var user = userTask.Result; 

     context.Result = new GrantValidationResult(user.Id, "password", null, "local", null); 
     return Task.FromResult(context.Result); 
    } 
} 

und

public class AspNetIdentityProfileService : IProfileService 
{ 
    private readonly UserManager<ApplicationUser> _userManager; 

    public AspNetIdentityProfileService(UserManager<ApplicationUser> userManager) 
    { 
     _userManager = userManager; 
    } 

    public async Task GetProfileDataAsync(ProfileDataRequestContext context) 
    { 
     var subject = context.Subject; 
     if (subject == null) throw new ArgumentNullException(nameof(context.Subject)); 

     var subjectId = subject.GetSubjectId(); 

     var user = await _userManager.FindByIdAsync(subjectId); 
     if (user == null) 
      throw new ArgumentException("Invalid subject identifier"); 

     var claims = await GetClaimsFromUser(user); 

     var siteIdClaim = claims.SingleOrDefault(x => x.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"); 
     context.IssuedClaims.Add(new Claim(JwtClaimTypes.Email, user.Email)); 
     context.IssuedClaims.Add(new Claim("siteid", siteIdClaim.Value)); 
     context.IssuedClaims.Add(new Claim(JwtClaimTypes.Role, "User")); 

     var roleClaims = claims.Where(x => x.Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"); 
     foreach (var roleClaim in roleClaims) 
     { 
      context.IssuedClaims.Add(new Claim(JwtClaimTypes.Role, roleClaim.Value)); 
     } 
    } 

    public async Task IsActiveAsync(IsActiveContext context) 
    { 
     var subject = context.Subject; 
     if (subject == null) throw new ArgumentNullException(nameof(context.Subject)); 

     var subjectId = subject.GetSubjectId(); 
     var user = await _userManager.FindByIdAsync(subjectId); 

     context.IsActive = false; 

     if (user != null) 
     { 
      if (_userManager.SupportsUserSecurityStamp) 
      { 
       var security_stamp = subject.Claims.Where(c => c.Type == "security_stamp").Select(c => c.Value).SingleOrDefault(); 
       if (security_stamp != null) 
       { 
        var db_security_stamp = await _userManager.GetSecurityStampAsync(user); 
        if (db_security_stamp != security_stamp) 
         return; 
       } 
      } 

      context.IsActive = 
       !user.LockoutEnabled || 
       !user.LockoutEnd.HasValue || 
       user.LockoutEnd <= DateTime.Now; 
     } 
    } 

    private async Task<IEnumerable<Claim>> GetClaimsFromUser(ApplicationUser user) 
    { 
     var claims = new List<Claim> 
     { 
      new Claim(JwtClaimTypes.Subject, user.Id), 
      new Claim(JwtClaimTypes.PreferredUserName, user.UserName) 
     }; 

     if (_userManager.SupportsUserEmail) 
     { 
      claims.AddRange(new[] 
      { 
       new Claim(JwtClaimTypes.Email, user.Email), 
       new Claim(JwtClaimTypes.EmailVerified, user.EmailConfirmed ? "true" : "false", ClaimValueTypes.Boolean) 
      }); 
     } 

     if (_userManager.SupportsUserPhoneNumber && !string.IsNullOrWhiteSpace(user.PhoneNumber)) 
     { 
      claims.AddRange(new[] 
      { 
       new Claim(JwtClaimTypes.PhoneNumber, user.PhoneNumber), 
       new Claim(JwtClaimTypes.PhoneNumberVerified, user.PhoneNumberConfirmed ? "true" : "false", ClaimValueTypes.Boolean) 
      }); 
     } 

     if (_userManager.SupportsUserClaim) 
     { 
      claims.AddRange(await _userManager.GetClaimsAsync(user)); 
     } 

     if (_userManager.SupportsUserRole) 
     { 
      var roles = await _userManager.GetRolesAsync(user); 
      claims.AddRange(roles.Select(role => new Claim(JwtClaimTypes.Role, role))); 
     } 

     return claims; 
    } 
} 

Sobald Sie diejenigen haben, die sie benötigen, um Ihre Dienste in startup.cs hinzugefügt werden:

services.AddTransient<IResourceOwnerPasswordValidator, ResourceOwnerPasswordValidator>(); 
services.AddTransient<IProfileService, AspNetIdentityProfileService>(); 

Hier ein kurzer Blick auf meine config :

public static IEnumerable<IdentityResource> GetIdentityResources() 
{ 
    return new List<IdentityResource> 
    { 
     new IdentityResources.OpenId() 
    }; 
} 

public static IEnumerable<ApiResource> GetApiResources() 
{ 
    return new List<ApiResource> 
    { 
     new ApiResource 
     { 
      Name = "api1", 
      Description = "My Api", 
      Scopes = 
      { 
       new Scope() 
       { 
        Name = "api1", 
        DisplayName = "Full access to Api" 
       } 
      } 
     } 
    }; 
} 

public static IEnumerable<Client> GetClients() 
{ 
    return new List<Client> 
    { 
     new Client 
     { 
      ClientId = "apiClient", 
      ClientName = "Api Angular2 Client", 
      AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, 
      AlwaysSendClientClaims = true, 
      AlwaysIncludeUserClaimsInIdToken = true, 
      ClientSecrets = 
      { 
       new Secret("secret".Sha256()) 
      }, 

      AllowedScopes = 
      { 
       "api1" 
      } 
     } 
    }; 
} 

Danach wird ein Aufruf an den Identitätsserver von einem Client:

var discoTask = DiscoveryClient.GetAsync("http://localhost:5000"); 
var disco = discoTask.Result; 

var tokenClient = new TokenClient(disco.TokenEndpoint, "apiClient", "secret"); 
var tokenResponseTask = tokenClient.RequestResourceOwnerPasswordAsync("[email protected]", "my-password", "api1"); 

var tokenResponse = tokenResponseTask.Result; 
var accessToken = tokenResponse.AccessToken; 

if (tokenResponse.IsError) 
{ 
    Console.WriteLine(tokenResponse.Error); 
    return; 
} 

das Token bei jwt.io Überprüfen Sie und Ihre Ergebnisse sehen ...

Verwandte Themen