[C#] Konfigurieren Sie den Endpunkt des Berechtigungsservers


Answers

Mit @ Pinpoint's Hilfe haben wir zusammen die Grundlagen einer Antwort verkabelt. Es zeigt, wie sich die Komponenten ohne Komplettlösung verteilen.

Fiedler-Demo

Mit unserem rudimentären Projektaufbau konnten wir in Fiddler die folgende Anfrage und Antwort anfordern.

Anfordern

POST http://localhost:50000/connect/token HTTP/1.1
User-Agent: Fiddler
Host: localhost:50000
Content-Length: 61
Content-Type: application/x-www-form-urlencoded

grant_type=password&username=my_username&password=my_password

Antwort

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 1687
Content-Type: application/json;charset=UTF-8
Expires: -1
X-Powered-By: ASP.NET
Date: Tue, 16 Jun 2015 01:24:42 GMT

{
  "access_token" : "eyJ0eXAiOi ... 5UVACg",
  "expires_in" : 3600,
  "token_type" : "bearer"
}

Die Antwort liefert ein Träger-Token, das wir verwenden können, um Zugang zum sicheren Teil der App zu erhalten.

Projektstruktur

Dies ist die Struktur unseres Projekts in Visual Studio. Wir mussten seine Properties > Debug > Port auf 50000 damit es als der Identitätsserver fungiert, den wir konfiguriert haben. Hier sind die relevanten Dateien:

ResourceOwnerPasswordFlow
    Providers
        AuthorizationProvider.cs
    project.json
    Startup.cs

Startup.cs

Zur Lesbarkeit habe ich die Startup Klasse in zwei Partials aufgeteilt.

Startup.ConfigureServices

Für die Grundlagen brauchen wir nur AddAuthentication() .

public partial class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthentication();
    }
}

Startup.Configure

public partial class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
        JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap.Clear();

        // Add a new middleware validating access tokens issued by the server.
        app.UseJwtBearerAuthentication(new JwtBearerOptions
        {
            AutomaticAuthenticate = true,
            AutomaticChallenge = true,
            Audience = "resource_server_1",
            Authority = "http://localhost:50000/",
            RequireHttpsMetadata = false
        });

        // Add a new middleware issuing tokens.
        app.UseOpenIdConnectServer(options =>
        {
            // Disable the HTTPS requirement.
            options.AllowInsecureHttp = true;

            // Enable the token endpoint.
            options.TokenEndpointPath = "/connect/token";

            options.Provider = new AuthorizationProvider();

            // Force the OpenID Connect server middleware to use JWT
            // instead of the default opaque/encrypted format.
            options.AccessTokenHandler = new JwtSecurityTokenHandler
            {
                InboundClaimTypeMap = new Dictionary<string, string>(),
                OutboundClaimTypeMap = new Dictionary<string, string>()
            };

            // Register an ephemeral signing key, used to protect the JWT tokens.
            // On production, you'd likely prefer using a signing certificate.
            options.SigningCredentials.AddEphemeralKey();
        });

        app.UseMvc();

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    }
}

AuthorizationProvider.cs

public sealed class AuthorizationProvider : OpenIdConnectServerProvider
{
    public override Task ValidateTokenRequest(ValidateTokenRequestContext context)
    {
        // Reject the token requests that don't use
        // grant_type=password or grant_type=refresh_token.
        if (!context.Request.IsPasswordGrantType() &&
            !context.Request.IsRefreshTokenGrantType())
        {
            context.Reject(
                error: OpenIdConnectConstants.Errors.UnsupportedGrantType,
                description: "Only grant_type=password and refresh_token " +
                             "requests are accepted by this server.");

            return Task.FromResult(0);
        }

        // Since there's only one application and since it's a public client
        // (i.e a client that cannot keep its credentials private), call Skip()
        // to inform the server that the request should be accepted without 
        // enforcing client authentication.
        context.Skip();

        return Task.FromResult(0);
    }

    public override Task HandleTokenRequest(HandleTokenRequestContext context)
    {
        // Only handle grant_type=password token requests and let the
        // OpenID Connect server middleware handle the other grant types.
        if (context.Request.IsPasswordGrantType())
        {
            // Validate the credentials here (e.g using ASP.NET Core Identity).
            // You can call Reject() with an error code/description to reject
            // the request and return a message to the caller.

            var identity = new ClaimsIdentity(context.Options.AuthenticationScheme);
            identity.AddClaim(OpenIdConnectConstants.Claims.Subject, "[unique identifier]");

            // By default, claims are not serialized in the access and identity tokens.
            // Use the overload taking a "destinations" parameter to make sure 
            // your claims are correctly serialized in the appropriate tokens.
            identity.AddClaim("urn:customclaim", "value",
                OpenIdConnectConstants.Destinations.AccessToken,
                OpenIdConnectConstants.Destinations.IdentityToken);

            var ticket = new AuthenticationTicket(
                new ClaimsPrincipal(identity),
                new AuthenticationProperties(),
                context.Options.AuthenticationScheme);

            // Call SetResources with the list of resource servers
            // the access token should be issued for.
            ticket.SetResources("resource_server_1");

            // Call SetScopes with the list of scopes you want to grant
            // (specify offline_access to issue a refresh token).
            ticket.SetScopes("profile", "offline_access");

            context.Validate(ticket);
        }

        return Task.FromResult(0);
    }
}

Projekt.json

{
  "dependencies": {
    "AspNet.Security.OpenIdConnect.Server": "1.0.0",
    "Microsoft.AspNetCore.Authentication.JwtBearer": "1.0.0",
    "Microsoft.AspNetCore.Mvc": "1.0.0",
  }

  // other code omitted
}
Question

Frage

Wie verwenden wir ein Träger-Token mit ASP.NET 5 mit einem Benutzernamen und Passwort-Flow? Für unser Szenario möchten wir einen Benutzer registrieren und sich mit AJAX-Anrufen anmelden, ohne eine externe Anmeldung verwenden zu müssen.

Um dies zu tun, müssen wir einen Autorisierungs-Server-Endpunkt haben. In den vorherigen Versionen von ASP.NET würden wir das folgende tun und dann bei der ourdomain.com/Token URL ourdomain.com/Token .

// Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
    TokenEndpointPath = new PathString("/Token"),
    Provider = new ApplicationOAuthProvider(PublicClientId),
    AccessTokenExpireTimeSpan = TimeSpan.FromDays(14)
};

In der aktuellen Version von ASP.NET, aber das oben funktioniert nicht. Wir haben versucht, den neuen Ansatz herauszufinden. Aspnet / identity Beispiel auf GitHub, zum Beispiel, konfiguriert Facebook, Google und Twitter-Authentifizierung, aber scheint nicht, um einen nicht-externen OAuth Authorization Server Endpunkt zu konfigurieren, es sei denn, was ist AddDefaultTokenProviders() tut, in diesem Fall fragen wir uns, was die URL an den Anbieter wäre.

Forschung

Wir haben gelernt , die Quelle hier zu lesen, dass wir die "Träger-Authentifizierungs-Middleware" der HTTP-Pipeline hinzufügen können, indem sie IAppBuilder.UseOAuthBearerAuthentication in unserer Startup Klasse IAppBuilder.UseOAuthBearerAuthentication . Dies ist ein guter Anfang, obwohl wir noch nicht sicher sind, wie man seinen Token-Endpunkt festlegt. Das hat nicht geklappt

public void Configure(IApplicationBuilder app)
{  
    app.UseOAuthBearerAuthentication(options =>
    {
        options.MetadataAddress = "meta";
    });

    // if this isn't here, we just get a 404
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Hello World.");
    });
}

ourdomain.com/meta wir auf unsere ourdomain.com/meta gehen, ourdomain.com/meta wir unsere Hallo Welt Seite.

Weitere Untersuchungen zeigten, dass wir auch die IAppBuilder.UseOAuthAuthentication Erweiterungsmethode verwenden können und dass es einen OAuthAuthenticationOptions Parameter benötigt. Dieser Parameter hat eine TokenEndpoint Eigenschaft. Also, obwohl wir uns nicht sicher sind, was wir tun, haben wir das versucht, was natürlich nicht funktioniert hat.

public void Configure(IApplicationBuilder app)
{
    app.UseOAuthAuthentication("What is this?", options =>
    {
        options.TokenEndpoint = "/token";
        options.AuthorizationEndpoint = "/oauth";
        options.ClientId = "What is this?";
        options.ClientSecret = "What is this?";
        options.SignInScheme = "What is this?";
        options.AutomaticAuthentication = true;
    });

    // if this isn't here, we just get a 404
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Hello World.");
    });
}

Mit anderen Worten, im Gehen zu ourdomain.com/token , gibt es keinen Fehler gibt es gerade wieder unsere Hallo Welt Seite.