web-dev-qa-db-ja.com

承認サーバーエンドポイントを構成する

質問

ユーザー名とパスワードのフローを使用して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 example GitHubでは、たとえば、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_に移動しても、エラーはありません。ここでも、こんにちは世界のページがあります。

38
Shaun Luttin

さて、OWIN/Katana 3によって提供されたさまざまなOAuth2ミドルウェア(およびそれぞれのIAppBuilder拡張機能)と、 ASP.NET Coreに移植されます:

  • app.UseOAuthBearerAuthentication/OAuthBearerAuthenticationMiddleware:その名前はそれほど明白ではありませんでしたが、OAuth2サーバーミドルウェアによって発行されたアクセストークンの検証を担当していました(ASP.NETコアに移植されたため、今でもそうです)。これは基本的に、Cookieミドルウェアのトークン対応物であり、APIを保護するために使用されます。 ASP.NET Coreでは、オプションのOpenID Connect機能で強化されています(OpenID Connectサーバーから署名証明書を自動的に取得できるようになりましたトークンを発行した)。

注:ASP.NET Core beta8以降、namedapp.UseJwtBearerAuthentication/JwtBearerAuthenticationMiddlewareになりました。

  • app.UseOAuthAuthorizationServer/OAuthAuthorizationServerMiddleware:名前が示すように、OAuthAuthorizationServerMiddlewareはOAuth2承認サーバーミドルウェアであり、アクセストークンの作成と発行に使用されました。 このミドルウェアはASP.NET Coreに移植されませんASP.NET CoreのOAuth認証サービス

  • app.UseOAuthBearerTokens:この拡張機能は実際にはミドルウェアに対応せず、単にapp.UseOAuthAuthorizationServerapp.UseOAuthBearerAuthenticationのラッパーです。 ASP.NET Identityパッケージの一部であり、1回の呼び出しでアクセストークンを検証するために使用されるOAuth2承認サーバーとOAuth2ベアラミドルウェアの両方を構成する便利な方法でした。 ASP.NET Coreに移植されません

ASP.NET Coreはまったく新しいミドルウェアを提供します(そして、私が設計したと言って誇りに思います)

  • app.UseOAuthAuthentication/OAuthAuthenticationMiddleware:この新しいミドルウェアは、app.UseFacebookAuthenticationまたはapp.UseGoogleAuthenticationとまったく同じように動作する汎用OAuth2インタラクティブクライアントですが、お客様を含むほぼすべての標準OAuth2プロバイダーをサポートします。 Google、Facebook、Microsoftプロバイダーはすべて、この新しいベースミドルウェアを継承するように更新されています。

したがって、実際に探しているミドルウェアはOAuth2認可サーバーミドルウェア、別名OAuthAuthorizationServerMiddlewareです。

コミュニティの大部分で不可欠なコンポーネントと見なされていますが、ASP.NET Coreには移植されません。

幸いなことに、すでに直接の代替品があります:AspNet.Security.OpenIdConnect.Server(- https://github.com/aspnet-contrib/ AspNet.Security.OpenIdConnect.Server

このミドルウェアは、Katana 3に付属しているOAuth2承認サーバーミドルウェアの高度なフォークですが、ターゲット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サンプル が機能する仕組みを理解しようとすることをためらわないでください。 関連するブログ投稿 を読むことをお勧めします。これは、リソース所有者のパスワードフローを実装する方法を説明しています。

それでも支援が必要な場合は、お気軽にご連絡ください。幸運を!

47
Pinpoint

@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_に設定して、構成したIDサーバーとして機能するようにしました。関連ファイルは次のとおりです。

_ResourceOwnerPasswordFlow
    Providers
        AuthorizationProvider.cs
    project.json
    Startup.cs
_

Startup.cs

読みやすくするために、Startupクラスを2つのパーシャルに分割しました。

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
}
_
31
Shaun Luttin