2017-02-27 22 views
5

Próba sprawdzenia poprawności tokena za pomocą Introspection Endpoint na IdentityServer4. Wciąż dostaję 401: Nieautoryzowane. Mój log wygląda tak:IdentityServer4 Introspection Endpoint API używa nieprawidłowego algorytmu mieszania

dbug: IdentityServer4.EntityFramework.Stores.ResourceStore[0] 
     Found MyAPI API resource in database 
info: IdentityServer4.Validation.HashedSharedSecretValidator[0] 
     Secret: MyAPI API uses invalid hashing algorithm. 
dbug: IdentityServer4.Validation.SecretValidator[0] 
     Secret validators could not validate secret 
fail: IdentityServer4.Validation.ApiSecretValidator[0] 
     API validation failed. 
fail: IdentityServer4.Endpoints.IntrospectionEndpoint[0] 
     API unauthorized to call introspection endpoint. aborting. 

mój API jest skonfigurowany tak:

new ApiResource 
       { 
        Name = "MyAPI", 
        DisplayName = "My API", 
        ApiSecrets = 
        { 
         new Secret("TopSecret".Sha256()) 
        }, 
       } 

olewam nagłówki typu zawartości jako application/x-www-form-urlencoded i autoryzacja jako podstawowe xxxxxxxxxxxxxxxxx gdzie x jest moim zakodowanym w standardzie 64 ciągiem znaków (myapi: TopSecret). Mój token znajduje się w treści posta

Czego mi brakuje? Dlaczego otrzymuję komunikat "MyAPI API wykorzystuje nieprawidłowy algorytm mieszający"? Jeśli jest nieprawidłowy, co to jest poprawny algorytm mieszający?

Informacje dodatkowe: Moje zasoby są zawarte w SQL db dostępnym poprzez Entity Framework. W szczególności konfiguracja jest taka sama, jak w znalezionej wcześniej skróconej dokumentacji here. Aby dojść do rzeczy, w której się znajdowałem, musiałem ręcznie dodać mój interfejs API do tabeli ApiSecrets i nadać mu typ (SharedSecret) i wartość, która jest hasłem Sha256.

W Startup.cs moi COnfigureServices obejmuje

services.AddIdentityServer() 
      .AddTemporarySigningCredential() 
      .AddInMemoryApiResources(Configurations.Scopes.GetApiResources()) 
      .AddInMemoryClients(Configurations.Clients.GetClients()) 

      .AddConfigurationStore(builder => 
       builder.UseSqlServer(connectionString, options => 
        options.MigrationsAssembly(migrationsAssembly))) 
      .AddOperationalStore(builder => 
       builder.UseSqlServer(connectionString, options => 
        options.MigrationsAssembly(migrationsAssembly))); 

     // include the password validation routine 
     services.AddTransient<IResourceOwnerPasswordValidator, Configurations.ResourceOwnerPasswordValidator>(); 
     services.AddTransient<IProfileService, Configurations.ProfileService>(); 

     services.AddMvc(); 

Pod Configure

app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions 
     { 
      Authority = "http://localhost:5000", 
      RequireHttpsMetadata = false, 
      ApiSecret = "TopSecret", 
      AutomaticAuthenticate = true, 
      AutomaticChallenge = false, 

      ApiName = "MyAPI" 
     }); 

     InitializeDatabase(app); 

     app.UseIdentityServer(); 

     app.UseMvc(); 

Zauważ, że dodałem ApiSecret, AutomaticAuthenticate i AutomaticChallenge do tej sekcji dopiero zacząłem mieć problem w wysiłek, aby to zadziałało.

W moich Scopes.cs Mam następujący API nakreślono:

public static IEnumerable<ApiResource> GetApiResources() 
    { 
     return new[] 
     { 
      new ApiResource 
      { 
       Name = "MyAPI", 
       DisplayName = "My API", 
       ApiSecrets = 
       { 
        new Secret("TopSecret".Sha256()), 
       }, 

      } 
     };    
    } 

Dla Clients.cs:

public static IEnumerable<Client> GetClients() 
    { 
     return new List<Client> 
    { 

     new Client 
     { 
      ClientName = "My Client", 
      AlwaysSendClientClaims=true,     
      ClientId = "MyClient", 
      ClientSecrets = { new Secret("TopSecret".Sha256()) }, 
      RequireClientSecret=false, 
      AllowAccessTokensViaBrowser =true, 
      AllowedGrantTypes = GrantTypes.HybridAndClientCredentials, 
      AllowedScopes = { "MyAPI" }, 
      RequireConsent = false, 
      AllowOfflineAccess = true, 

     }, 

To mniej więcej wszystko, co jest do części kodu. Baza danych, w której znajduje się konfiguracja, wydaje się przesłonić wszelkie zmiany kodu, które wprowadzam, więc nie jestem pewien, na ile to wszystko jest użyteczne. W DB utworzyłem rekord w tabeli ApiSecrets z ApiResourceId z 1, dodano opis i datę wygaśnięcia, ustawiłem Type na "SharedSecret" i dodałem Secret używając różnych formatów, w tym zwykłego tekstu, sha256 i base64.

Oto pełny dziennik podczas połączenia. Być może to pomoże. Widzę, że są rzeczy, których nie można znaleźć na okaziciela lub coś takiego, ale nie jestem pewien, dlaczego tak się stało i czy ma to wpływ na wynik procedury.

info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2] 
     Request finished in 29.4277ms 401 
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1] 
     Request starting HTTP/1.1 POST http://localhost:5000/connect/introspect application/x-www-form-urlencoded 762 
info: IdentityServer4.AccessTokenValidation.Infrastructure.NopAuthenticationMiddleware[7] 
     Bearer was not authenticated. Failure message: No token found. 
dbug: IdentityServer4.CorsPolicyProvider[0] 
     CORS request made for path: /connect/introspect from origin: chrome-extension://aicmkgpgakddgnaphhhpliifpcfhicfo but rejected because invalid CORS path 
info: Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware[7] 
     idsrv was not authenticated. Failure message: Unprotect ticket failed 
dbug: IdentityServer4.Hosting.EndpointRouter[0] 
     Request path /connect/introspect matched to endpoint type Introspection 
dbug: IdentityServer4.Hosting.EndpointRouter[0] 
     Mapping found for endpoint: Introspection, creating handler: IdentityServer4.Endpoints.IntrospectionEndpoint 
info: IdentityServer4.Hosting.IdentityServerMiddleware[0] 
     Invoking IdentityServer endpoint: IdentityServer4.Endpoints.IntrospectionEndpoint for /connect/introspect 
dbug: IdentityServer4.Endpoints.IntrospectionEndpoint[0] 
     Starting introspection request. 
dbug: IdentityServer4.Validation.BasicAuthenticationSecretParser[0] 
     Start parsing Basic Authentication secret 
dbug: IdentityServer4.Validation.SecretParser[0] 
     Parser found secret: BasicAuthenticationSecretParser 
dbug: IdentityServer4.Validation.SecretParser[0] 
     Secret id found: MyAPI 
info: Microsoft.EntityFrameworkCore.Storage.IRelationalCommandBuilderFactory[1] 
     Executed DbCommand (0ms) [Parameters=[@__name_0='?' (Size = 200)], CommandType='Text', CommandTimeout='30'] 
     SELECT TOP(1) [apiResource].[Id], [apiResource].[Description], [apiResource].[DisplayName], [apiResource].[Enabled], [apiResource].[Name] 
     FROM [ApiResources] AS [apiResource] 
     WHERE [apiResource].[Name] = @__name_0 
     ORDER BY [apiResource].[Id] 
info: Microsoft.EntityFrameworkCore.Storage.IRelationalCommandBuilderFactory[1] 
     Executed DbCommand (0ms) [Parameters=[@__name_0='?' (Size = 200)], CommandType='Text', CommandTimeout='30'] 
     SELECT [a3].[Id], [a3].[ApiResourceId], [a3].[Type] 
     FROM [ApiClaims] AS [a3] 
     INNER JOIN (
      SELECT DISTINCT TOP(1) [apiResource].[Id] 
      FROM [ApiResources] AS [apiResource] 
      WHERE [apiResource].[Name] = @__name_0 
      ORDER BY [apiResource].[Id] 
    ) AS [apiResource2] ON [a3].[ApiResourceId] = [apiResource2].[Id] 
     ORDER BY [apiResource2].[Id] 
info: Microsoft.EntityFrameworkCore.Storage.IRelationalCommandBuilderFactory[1] 
     Executed DbCommand (0ms) [Parameters=[@__name_0='?' (Size = 200)], CommandType='Text', CommandTimeout='30'] 
     SELECT [a2].[Id], [a2].[ApiResourceId], [a2].[Description], [a2].[Expiration], [a2].[Type], [a2].[Value] 
     FROM [ApiSecrets] AS [a2] 
     INNER JOIN (
      SELECT DISTINCT TOP(1) [apiResource].[Id] 
      FROM [ApiResources] AS [apiResource] 
      WHERE [apiResource].[Name] = @__name_0 
      ORDER BY [apiResource].[Id] 
    ) AS [apiResource1] ON [a2].[ApiResourceId] = [apiResource1].[Id] 
     ORDER BY [apiResource1].[Id] 
info: Microsoft.EntityFrameworkCore.Storage.IRelationalCommandBuilderFactory[1] 
     Executed DbCommand (0ms) [Parameters=[@__name_0='?' (Size = 200)], CommandType='Text', CommandTimeout='30'] 
     SELECT [a].[Id], [a].[ApiResourceId], [a].[Description], [a].[DisplayName], [a].[Emphasize], [a].[Name], [a].[Required], [a].[ShowInDiscoveryDocument] 
     FROM [ApiScopes] AS [a] 
     INNER JOIN (
      SELECT DISTINCT TOP(1) [apiResource].[Id] 
      FROM [ApiResources] AS [apiResource] 
      WHERE [apiResource].[Name] = @__name_0 
      ORDER BY [apiResource].[Id] 
    ) AS [apiResource0] ON [a].[ApiResourceId] = [apiResource0].[Id] 
     ORDER BY [apiResource0].[Id], [a].[Id] 
info: Microsoft.EntityFrameworkCore.Storage.IRelationalCommandBuilderFactory[1] 
     Executed DbCommand (0ms) [Parameters=[@__name_0='?' (Size = 200)], CommandType='Text', CommandTimeout='30'] 
     SELECT [a0].[Id], [a0].[ApiScopeId], [a0].[Type] 
     FROM [ApiScopeClaims] AS [a0] 
     INNER JOIN (
      SELECT DISTINCT [apiResource0].[Id], [a].[Id] AS [Id0] 
      FROM [ApiScopes] AS [a] 
      INNER JOIN (
       SELECT DISTINCT TOP(1) [apiResource].[Id] 
       FROM [ApiResources] AS [apiResource] 
       WHERE [apiResource].[Name] = @__name_0 
       ORDER BY [apiResource].[Id] 
     ) AS [apiResource0] ON [a].[ApiResourceId] = [apiResource0].[Id] 
    ) AS [a1] ON [a0].[ApiScopeId] = [a1].[Id0] 
     ORDER BY [a1].[Id], [a1].[Id0] 
dbug: IdentityServer4.EntityFramework.Stores.ResourceStore[0] 
     Found MyAPI API resource in database 
info: IdentityServer4.Validation.HashedSharedSecretValidator[0] 
     Secret: MyAPI Secret uses invalid hashing algorithm. 
info: IdentityServer4.Validation.HashedSharedSecretValidator[0] 
     Secret: MyAPI Secret uses invalid hashing algorithm. 
dbug: IdentityServer4.Validation.SecretValidator[0] 
     Secret validators could not validate secret 
fail: IdentityServer4.Validation.ApiSecretValidator[0] 
     API validation failed. 
fail: IdentityServer4.Endpoints.IntrospectionEndpoint[0] 
     API unauthorized to call introspection endpoint. aborting. 
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2] 
     Request finished in 30.673ms 401 
+0

Może możesz opublikować całe rozwiązanie VS, aby ułatwić ludziom debugowanie. – Evk

+0

Trudno powiedzieć, ale na podstawie opublikowanego przez Ciebie kodu wydaje się, że używasz swojego API i serwera tożsamości w jednej aplikacji. Czy może tu być mieszanka? Dlaczego trzeba było ręcznie dodawać zaszyfrowany ApiSecret? Czy popełniłeś błąd? Jeśli postępowałeś zgodnie z dokumentacją QS 'InitializeDatabase (app);' zapełni tylko twoją bazę danych, jeśli nie zawiera żadnych danych, co może być przyczyną, dla której twoje zmiany kodu nie przynoszą efektu. – user1336

Odpowiedz

5

Nie widząc najmniejszych szczegółów kodu i konfiguracji bazy danych, jest to trochę trudne. Ponadto nie widzę kodu, w którym faktycznie wywoływany jest punkt końcowy introspekcji. Czy robisz to w C# lub w JavaScript lub z Postman?

Tak czy inaczej, oto moja opinia ...

Startup.cs

Sposób ConfigureServices wygląda dobrze. Dodanie usługi walidatora haseł ResourceOwner jest zbędne dla podanego problemu; aby uzyskać dostęp do punktu końcowego introspekcji potrzebna jest aplikacja ApiSecret , a nie hasło ResourceOwner. Zakładam, że masz go tam z jakiegoś niepowiązanego powodu, jeśli nie, a potem go wyrzuć.

Zgodnie z metodą Konfiguracji masz app.UseIdentityServerAuthentication, co oznacza, że ​​używasz aplikacji internetowej, aby nie tylko działała jako serwer uwierzytelniający (przy użyciu IdentityServer4), ale jest to również aplikacja Web Api, która oddzwania do serwera uwierzytelniania (sama w sobie). w tym przypadku), aby potwierdzić przychodzące tokeny.

app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions 
{ 
    Authority = "https://localhost:44388", 
    RequireHttpsMetadata = false, 
    ApiName = "MyAPI" 
    //ApiSecret = "TopSecret" not necessary to know the api secret for normal validation 
    //AutomaticAuthenticate = true, not necessary 
    //AutomaticChallenge = false not necessary 
}); 

Możesz również chcieć app.UseMvcWithDefaultRoute().

Api WEW Konfiguracje

Używanie programu Setup new ApiResource("name", "display") konstruktor będzie bazę poprawnie; ale użycie składni inicjalizatora obiektu, tak jak powyżej, nie spowoduje. Jest to problem zgłoszone GitHub: https://github.com/IdentityServer/IdentityServer4/issues/836

public static IEnumerable<ApiResource> GetApiResources() 
{ 
    return new List<ApiResource> 
    { 
     // this will incorrectly leave out the ApiScope record in the database, but will create the ApiResoure and ApiSecret records 
     new ApiResource 
     { 
      Name = "MyAPI", 
      DisplayName = "My API", 
      ApiSecrets = 
      { 
       new Secret("TopSecret".Sha256()), 
      } 
     }, 
     // this will correctly create the ApiResource, ApiScope, and ApiSecret records in the database. 
     new ApiResource("MyAPI2", "My API2") 
     { 
      ApiSecrets = 
      { 
       new Secret("TopSecret2".Sha256()) 
      } 
     } 
    }; 
} 

FYI, ponieważ nie istnieją żadne celownicze określone w new ApiResources bezpośrednio powyżej, oprzyrządowania IdentityServer będzie autogenerować jeden ApiScope dla każdego ApiResource. ApiScore ma taką samą nazwę jak ApiResource w tym przypadku. ApiResource musi mieć przynajmniej jeden ApiScope; ale może mieć ich wiele. Jest to ApiScope, które są następnie powiązane z klientem w tabeli ClientScopes.

WEW Configuration Client

Zobacz komentarze ...

public static IEnumerable<Client> GetClients() 
{ 
    return new List<Client> 
    { 
     new Client 
     { 
      ClientName = "My Client", 
      AlwaysSendClientClaims = true, 
      ClientId = "MyClient", 
      // changed the secret to make clear this is unrelated to the Api secret 
      ClientSecrets = { new Secret("TopSecretClientSecret".Sha256()) }, 
      // RequireClientSecret might as well be true if you are giving this client a secret 
      RequireClientSecret = true, 
      AllowAccessTokensViaBrowser = true, 
      AllowedGrantTypes = GrantTypes.HybridAndClientCredentials, 
      // Added MyAPI2 from my example above 
      AllowedScopes = { "MyAPI", "MyAPI2" }, 
      RequireConsent = false, 
      AllowOfflineAccess = true 
     } 
    }; 
} 

Wywołanie introspekcji Endpoint

Poniższy kod jest ze sterownika WebAPI. (Pamiętaj, że autor IdentityServer i ApiResource są hostowane w tej samej aplikacji internetowej w tej dyskusji). Wniosek do tej metody zostanie złożony przez klienta.

Wewnątrz tej metody można zobaczyć wywołania do punktu końcowego introspekcji swojego urzędu w celu sprawdzenia poprawności/odszyfrowania hasła access_token. W tym przykładzie nie jest to konieczne, ponieważ konfigurujemy aplikację internetową pod numerem app.UseIdentityServerAuthentication, która już to robi. Punkt końcowy introspekcji byłby używany w przypadku Reference tokens lub w przypadku, gdy aplikacja internetowa sama nie jest w stanie zweryfikować właściwości access_token.

[Route("api/[controller]/[action]")] 
[Produces("application/json")] 
public class DataController : Controller 
{ 
    [HttpGet] 
    [Authorize] 
    public async Task<IEnumerable<String>> Secure() 
    { 
     var accessToken = await HttpContext.Authentication.GetTokenAsync("access_token"); 

     var introspectionClient = new IntrospectionClient("https://localhost:44388/connect/introspect", "MyAPI", "TopSecret"); 

     var response = await introspectionClient.SendAsync(new IntrospectionRequest { Token = accessToken }); 

     var isActive = response.IsActive; 
     var claims = response.Claims; 

     return new[] { "secure1", "secure2", $"isActive: {isActive}", JsonConvert.SerializeObject(claims) }; 
    } 
} 

Tutaj, przy użyciu IntrospectionClient dla ApiScope „MyAPI” dałoby 401, ponieważ baza danych brakuje ApiScope powodu problemu obiekt inicjatora wcześniej wspomniano.

Ostatnią rzeczą

Innym możliwym problemem jest to, że ręczne dodanie ApiSecret hashed w edytorze bazy danych może spowodować dziwne kopiuj/wklej tekst zagadnień wchodzących w stanie prawidłowo rozszyfrował.

Zobacz pełne rozwiązanie:

https://github.com/travisjs/AspNetCore-IdentityServer-Instrospection

Mam nadzieję, że to może pomóc dostać się do dolnej części emisji lub przynajmniej pobudzać nową myśl.

+0

Jest to najbardziej zwięzła odpowiedź do tej pory. Niestety nie mogę załadować twojego rozwiązania, ponieważ jestem na razie zwolniony do korzystania z VS2015 i wygląda na to, że twoje rozwiązanie wykorzystuje nową wersję beta 2017. Poza tym udało mi się sprawić, że część twojego kodu zadziałała. Wygląda na to, że miałem złe podejście i dostosowując własne zdanie, pobudziłeś nowe myśli z mojej strony. Wielkie uznanie, proszę pana! – Rafe

+0

@Rafe tak, właśnie przebudowałem swoją maszynę i zdecydowałem się nie instalować VS 2015; jeśli upuścisz cały kod do projektu VS 2015, powinien on działać - project.json to jedyna prawdziwa różnica. –

+0

Dzięki @ travis.js. Przyznam ci nagrodę za to. Zastanawiałem się jednak, czy byłoby to w porządku, gdybym wysłał do ciebie e-mail bezpośrednio w związku z problemem, z którym borykam się w tym projekcie? – Rafe

0

(myapi: TopSecret). Moja Reklamowe jest w treści postu

Punkt końcowy introspekcja wymaga uwierzytelniania podstawowego, który używa scope:apisecret, nie name:apisecret.

+0

Wpisałem to błędnie, to w rzeczywistości MyAPI: TopSecret, gdzie MyAPI jest zasięgiem. – Rafe