Настройка конечной точки сервера авторизации
Вопрос
Как мы используем токен-носитель с ASP.NET 5 с использованием потока имени пользователя и пароля? Для нашего сценария мы хотим, чтобы пользователь регистрировался и входил в систему с помощью AJAX-вызовов без необходимости использования внешнего входа.
Для этого нам нужно иметь конечную точку сервера авторизации. В предыдущих версиях ASP.NET мы выполнили следующие действия, а затем запустили бы с адресом 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)
};
В текущей версии 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
нет ошибки, мы снова вернем нашу всемирную страницу приветствия.
Ответы
Ответ 1
Хорошо, давайте вспомним другое промежуточное ПО 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.
-
app.UseOAuthBearerTokens
: это расширение действительно не соответствовало промежуточному программному обеспечению и было просто оберткой вокруг app.UseOAuthAuthorizationServer
и app.UseOAuthBearerAuthentication
. Он был частью пакета ASP.NET Identity и был просто удобным способом настройки сервера авторизации OAuth2 и промежуточного программного обеспечения OAuth2, используемого для проверки токенов доступа в одном вызове. Он не будет перенесен в ядро ASP.NET.
ASP.NET Core предложит совершенно новое промежуточное ПО (и я с гордостью могу сказать, что я его разработал):
-
app.UseOAuthAuthentication
/OAuthAuthenticationMiddleware
: это новое промежуточное программное обеспечение представляет собой общий интерактивный клиент OAuth2, который ведет себя точно так же, как app.UseFacebookAuthentication
или app.UseGoogleAuthentication
, но поддерживает практически любой стандартный поставщик OAuth2, включая ваш. Поставщики Google, Facebook и Microsoft все были обновлены, чтобы наследовать это новое базовое промежуточное программное обеспечение.
Итак, промежуточное программное обеспечение, которое вы действительно ищете, - это промежуточное программное обеспечение сервера авторизации OAuth2, aka 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.
Не стесняйтесь давать образец Postman, чтобы понять, как это работает. Я бы рекомендовал прочитать связанное сообщение в блоге, в котором объясняется, как вы можете реализовать поток паролей владельца ресурса.
Не стесняйтесь пинговать меня, если вам все еще нужна помощь.
Удачи!
Ответ 2
С помощью справки @Pinpoint мы связали рудименты ответа. Он показывает, как компоненты соединяются вместе, не будучи полным решением.
Демоверсия Fiddler
С нашей рудиментарной настройкой проекта мы смогли сделать следующий запрос и ответ в 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 only one application and since it 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
}