配置授权服务器终结点



问题

如何使用用户名和密码流将持有者令牌与 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 中,上述内容不起作用。我们一直在努力找出新的方法。例如,GitHub上的aspnet/identity示例配置了Facebook,Google和Twitter身份验证,但似乎没有配置非外部OAuth授权服务器端点,除非这是AddDefaultTokenProviders()所做的,在这种情况下,我们想知道提供程序的URL是什么。

研究

我们从阅读此处的源代码中了解到,我们可以通过在 Startup 类中调用 IAppBuilder.UseOAuthBearerAuthentication 将"持有者身份验证中间件"添加到 HTTP 管道中。这是一个良好的开端,尽管我们仍然不确定如何设置其令牌终结点。这不起作用:

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我们只是收到我们的hello world页面。

进一步的研究表明,我们也可以使用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,没有错误,只是再次出现我们的hello world页面。

编辑(01/28/2021):AspNet.Security.OpenIdConnect.Server已作为3.0更新的一部分合并到OpenIddict中。要开始使用OpenIddict,请访问 documentation.openiddict.com。

<小时 />

好的,让我们回顾一下OWIN/Katana3提供的不同OAuth2中间件(及其各自的IAppBuilder扩展)以及将移植到 ASP.NET 核心的中间件:

  • 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 核心:ASP.NET 核心中的 OAuth 授权服务。

  • app.UseOAuthBearerTokens:这个扩展并不真正对应于中间件,只是app.UseOAuthAuthorizationServerapp.UseOAuthBearerAuthentication的包装器。它是 ASP.NET 标识包的一部分,只是在单个调用中配置 OAuth2 授权服务器和用于验证访问令牌的 OAuth2 持有者中间件的便捷方法。它不会移植到 ASP.NET 核心

ASP.NET Core将提供一个全新的中间件(我很自豪地说我设计了它):

  • app.UseOAuthAuthentication/OAuthAuthenticationMiddleware :这个新的中间件是一个通用的OAuth2交互式客户端,其行为与app.UseFacebookAuthenticationapp.UseGoogleAuthentication完全相同,但几乎支持任何标准的OAuth2提供程序,包括您的提供程序。Google,Facebook和Microsoft提供商都已更新为继承了这个新的基础中间件。
<小时 />

因此,您实际寻找的中间件是 OAuth2 授权服务器中间件,又名 OAuthAuthorizationServerMiddleware

虽然它被社区的大部分人视为必不可少的组成部分,但它不会被移植到 ASP.NET Core

幸运的是,已经有一个直接的替代品:AspNet.Security.OpenIdConnect.Server(https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server)。

该中间件

是 OAuth2 授权服务器中间件的高级分支,它随 Katana3 一起提供,但面向 OpenID Connect(它本身基于 OAuth2)。它使用相同的低级方法,提供细粒度控制(通过各种通知),并允许您使用自己的框架(Nancy,ASP.NET Core MVC)来提供授权页面,就像使用 OAuth2 服务器中间件一样。配置它很简单:

ASP.NET 核心 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 酷睿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/Katana3版本,以及一个同时支持.NET Desktop和.NET Core的 ASP.NET Core版本。

不要犹豫,尝试给邮递员样本,了解它是如何工作的。我建议阅读相关的博客文章,其中解释了如何实现资源所有者密码流。

如果您仍然需要帮助,请随时 ping 我。祝你好运!

在@Pinpoint的帮助下,我们已经将答案的基本要素连接在一起。它显示了组件如何连接在一起,而不是一个完整的解决方案。

小提琴手演示

通过我们基本的项目设置,我们能够在 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

启动.cs

为了便于阅读,我将Startup类分为两个部分。

Startup.ConfigureServices

对于最基本的,我们只需要 AddAuthentication() .

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

启动.配置

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

授权提供程序.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);
    }
}

项目.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
}

相关内容

  • 没有找到相关文章

最新更新