Настройте конечную точку сервера авторизации

Вопрос

Как мы используем токен на предъявителя в ASP.NET 5, используя поток имени пользователя и пароля? В нашем сценарии мы хотим позволить пользователю зарегистрироваться и войти в систему с помощью вызовов AJAX без необходимости использования внешнего входа в систему.

Для этого нам нужна конечная точка сервера авторизации. В предыдущих версиях ASP.NET мы делали следующее, а затем входили в ourdomain.com/Token URL.

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

Однако в текущей версии ASP.NET вышеприведенное не работает. Мы пытались выяснить новый подход. Например, aspnet/identity на GitHub настраивает аутентификацию Facebook, Google и Twitter, но, похоже, не настраивает внешнюю конечную точку сервера авторизации OAuth, если только это не то, что AddDefaultTokenProviders() делает, и в этом случае мы задаемся вопросом, каким будет URL для провайдера.

Исследование

Из прочитанного здесь источника мы узнали, что мы можем добавить "промежуточное ПО аутентификации на предъявителя" в конвейер HTTP, вызвав IAppBuilder.UseOAuthBearerAuthentication в нашем Startup учебный класс. Это хорошее начало, хотя мы до сих пор не уверены, как установить конечную точку токена. Это не сработало:

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 мы только что получили нашу привет страницу мира.

Дальнейшие исследования показали, что мы также можем использовать IAppBuilder.UseOAuthAuthentication метод расширения, и что требуется OAuthAuthenticationOptions параметр. Этот параметр имеет TokenEndpoint имущество. Поэтому, хотя мы не уверены в том, что делаем, мы попробовали это, что, конечно, не сработало.

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.");
    });
}

Другими словами, при переходе к ourdomain.com/token, нет ошибки, просто снова наша страница "Привет, мир!"

2 ответа

Решение

Хорошо, давайте вспомним другое промежуточное ПО OAuth2 (и их соответствующие IAppBuilder расширения), которые были предложены OWIN/Katana 3 и те, которые будут перенесены в ASP.NET Core:

  • app.UseOAuthBearerAuthentication / OAuthBearerAuthenticationMiddleware: его имя было не совсем очевидным, но оно было (и по-прежнему таково, как оно было перенесено на ASP.NET Core), отвечающее за проверку токенов доступа, выпущенных промежуточным ПО сервера OAuth2. По сути, это токен-аналог промежуточного программного обеспечения cookie и используется для защиты ваших API. В ASP.NET Core он был дополнен дополнительными функциями OpenID Connect (теперь он может автоматически извлекать сертификат подписи с сервера OpenID Connect, выпустившего токены).

Примечание: начиная с ASP.NET Core beta8, теперь он называется app.UseJwtBearerAuthentication / JwtBearerAuthenticationMiddleware,

  • app.UseOAuthAuthorizationServer / OAuthAuthorizationServerMiddleware: как следует из названия, OAuthAuthorizationServerMiddleware был промежуточным ПО сервера авторизации OAuth2 и использовался для создания и выдачи токенов доступа. Это промежуточное программное обеспечение не будет перенесено в ASP.NET Core: службу авторизации OAuth в ASP.NET Core.

  • app.UseOAuthBearerTokens: это расширение на самом деле не соответствовало промежуточному программному обеспечению и было просто оберткой вокруг app.UseOAuthAuthorizationServer а также app.UseOAuthBearerAuthentication, Он был частью пакета удостоверений ASP.NET и был просто удобным способом настройки как сервера авторизации OAuth2, так и промежуточного программного обеспечения носителя OAuth2, используемого для проверки токенов доступа за один вызов. Он не будет перенесен в ASP.NET Core.

ASP.NET Core предложит совершенно новое промежуточное ПО (и я с гордостью могу сказать, что разработал его):

  • app.UseOAuthAuthentication / OAuthAuthenticationMiddleware: это новое промежуточное ПО - это универсальный интерактивный клиент OAuth2, который ведет себя точно так же app.UseFacebookAuthentication или же app.UseGoogleAuthentication но это поддерживает практически любого стандартного поставщика OAuth2, включая вашего. Google, Facebook и Microsoft провайдеры были обновлены, чтобы наследовать от этого нового базового промежуточного программного обеспечения.

Итак, промежуточное ПО, которое вы на самом деле ищете, это промежуточное ПО сервера авторизации OAuth2, иначе OAuthAuthorizationServerMiddleware,

Хотя большая часть сообщества считает его важным компонентом, он не будет перенесен в ASP.NET Core.

К счастью, уже есть прямая замена: AspNet.Security.OpenIdConnect.Server ( https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server)

Это промежуточное ПО представляет собой расширенную ветвь промежуточного ПО сервера авторизации OAuth2, поставляемого с Katana 3, но предназначенного для OpenID Connect (который сам основан на OAuth2). Он использует тот же низкоуровневый подход, который предлагает детальное управление (через различные уведомления) и позволяет вам использовать собственную платформу (Nancy, ASP.NET Core MVC) для обслуживания ваших страниц авторизации, как вы могли бы с промежуточным ПО сервера OAuth2, Настроить это легко:

ASP.NET Core 1.x:

// Add a new middleware validating access tokens issued by the server.
app.UseOAuthValidation();

// Add a new middleware issuing tokens.
app.UseOpenIdConnectServer(options =>
{
    options.TokenEndpointPath = "/connect/token";

    // Create your own `OpenIdConnectServerProvider` and override
    // ValidateTokenRequest/HandleTokenRequest to support the resource
    // owner password flow exactly like you did with the OAuth2 middleware.
    options.Provider = new AuthorizationProvider();
});

ASP.NET Core 2.x:

// Add a new middleware validating access tokens issued by the server.
services.AddAuthentication()
    .AddOAuthValidation()

    // Add a new middleware issuing tokens.
    .AddOpenIdConnectServer(options =>
    {
        options.TokenEndpointPath = "/connect/token";

        // Create your own `OpenIdConnectServerProvider` and override
        // ValidateTokenRequest/HandleTokenRequest to support the resource
        // owner password flow exactly like you did with the OAuth2 middleware.
        options.Provider = new AuthorizationProvider();
    });

Существует версия OWIN/Katana 3 и версия ASP.NET Core, которая поддерживает как.NET Desktop, так и.NET Core.

Не стесняйтесь дать образцу Почтальона попытку понять, как он работает. Я бы рекомендовал прочитать соответствующий пост в блоге, в котором объясняется, как реализовать поток паролей владельца ресурса.

Не стесняйтесь пинговать меня, если вам все еще нужна помощь. Удачи!

С помощью @Pinpoint мы соединили зачатки ответа. Это показывает, как компоненты соединяются вместе, не будучи полным решением.

Fiddler Demo

С нашей элементарной настройкой проекта мы смогли сделать следующий запрос и ответ в Fiddler.

Запрос

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

отклик

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"
}

В ответе предоставляется токен на предъявителя, который мы можем использовать для получения доступа к защищенной части приложения.

Структура проекта

Это структура нашего проекта в Visual Studio. Мы должны были установить его Properties > Debug > Port в 50000 так что он действует как сервер идентификации, который мы настроили. Вот соответствующие файлы:

ResourceOwnerPasswordFlow
    Providers
        AuthorizationProvider.cs
    project.json
    Startup.cs

Startup.cs

Для удобства чтения я разделил Startup разделить на две части.

Startup.ConfigureServices

Для самых основ нам нужно только 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);
    }
}

project.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
}
Другие вопросы по тегам