web-dev-qa-db-ja.com

同じエンドポイントのCookieとBearer Authorizationを組み合わせたASP.NET Core 2.0

「Web Application(Model-View-Controller)」テンプレートと「.Net Framework」+「ASP.NET Core 2」を構成として使用して、VS17に新しいASP.NET Core Webアプリケーションプロジェクトを作成しました。認証設定は「個人ユーザーアカウント」に設定されます。

次のサンプルエンドポイントがあります。

_[Produces("application/json")]
[Route("api/price")]
[Authorize(Roles = "PriceViwer", AuthenticationSchemes = "Cookies,Bearer")]
public class PriceController : Controller
{

    public IActionResult Get()
    {
        return Ok(new Dictionary<string, string> { {"Galleon/Pound",
                                                   "999.999" } );
    }
}
_

_"Cookies,Bearer"_は、_CookieAuthenticationDefaults.AuthenticationScheme_と_JwtBearerDefaults.AuthenticationScheme_を連結することにより導出されます。

目的は、トークンとCookieの両方の認証方法を使用してエンドポイントにアクセスできるように、エンドポイントの承認を構成できるようにすることです。

Startup.csでの認証のセットアップを次に示します。

_    services.AddAuthentication()
        .AddCookie(cfg => { cfg.SlidingExpiration = true;})
        .AddJwtBearer(cfg => {
            cfg.RequireHttpsMetadata = false;
            cfg.SaveToken = true;
            cfg.TokenValidationParameters = new TokenValidationParameters() {
                                                    ValidIssuer = Configuration["Tokens:Issuer"],
                                                    ValidAudience = Configuration["Tokens:Issuer"],
                                                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Tokens:Key"]))
                                                };
        });
_

そのため、ブラウザーを使用してエンドポイントにアクセスしようとすると、空白のhtmlページで401応答が返されます。
I get the 401 response with a blank html page.

その後、ログインし、エンドポイントに再度アクセスしようとすると、同じ応答が返されます。

次に、ベアラートークンを指定してエンドポイントにアクセスしようとします。そして、それは200応答で目的の結果を返します。
And that returns the desired result with the 200 response.

したがって、[Authorize(AuthenticationSchemes = "Cookies,Bearer")]を削除すると、状況は逆になります-Cookie認証は機能し、200を返しますが、上記で使用したのと同じベアラートークンメソッドでは結果が得られず、デフォルトのAspIdentityログインにリダイレクトされますページ。

ここで2つの問題を確認できます。

1)ASP.NET Coreは「複合」認証を許可しません。 2)「Cookie」は有効なスキーマ名ではありません。しかし、次に使用するのに適切なものは何ですか?

お知らせ下さい。ありがとうございました。

18
maximiniini

AuthenticationSchemeをコントローラーに設定する必要はないと思います。次のように、ConfigureServicesで認証済みユーザーを使用します。

// requires: using Microsoft.AspNetCore.Authorization;
//           using Microsoft.AspNetCore.Mvc.Authorization;
services.AddMvc(config =>
{
    var policy = new AuthorizationPolicyBuilder()
                     .RequireAuthenticatedUser()
                     .Build();
    config.Filters.Add(new AuthorizeFilter(policy));
});

私のソースのドキュメント: registerAuthorizationHandlers

部分については、scheme-Keyが有効でなかったかどうかに関係なく、補間された文字列を使用して正しいキーを使用できます。

[Authorize(AuthenticationSchemes = $"{CookieAuthenticationDefaults.AuthenticationScheme},{JwtBearerDefaults.AuthenticationScheme}")]

編集:私はさらに調査を行い、次の結論に達しました:2つのスキームOr-Likeでメソッドを承認することはできませんが、2つのパブリックメソッドを使用して、このようなプライベートメソッドを呼び出すことができます:

//private method
private IActionResult GetThingPrivate()
{
   //your Code here
}

//Jwt-Method
[Authorize(AuthenticationSchemes = $"{JwtBearerDefaults.AuthenticationScheme}")]
[HttpGet("bearer")]
public IActionResult GetByBearer()
{
   return GetThingsPrivate();
}

 //Cookie-Method
[Authorize(AuthenticationSchemes = $"{CookieAuthenticationDefaults.AuthenticationScheme}")]
[HttpGet("cookie")]
public IActionResult GetByCookie()
{
   return GetThingsPrivate();
}
10
Nikolaus

質問を正しく理解していれば、解決策があると思います。次の例では、1つのアプリでCookieとベアラー認証を使用しています。 _[Authorize]_属性はスキームを指定せずに使用でき、使用されている承認方法に応じて、アプリは動的に反応します。

_services.AddAuthentication_を2回呼び出して、2つの認証スキームを登録します。 ソリューションの鍵は、コードスニペットの最後にある_services.AddAuthorization_の呼び出しであり、ASP.NETに両方のスキームを使用するよう指示します。

私はこれをテストしましたが、うまくいくようです。

Microsoft docs に基づきます。)

_services.AddAuthentication(options =>
    {
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = "oidc";
    })
    .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddOpenIdConnect("oidc", options =>
    {
        options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.Authority = "https://localhost:4991";
        options.RequireHttpsMetadata = false;

        options.ClientId = "WebApp";
        options.ClientSecret = "secret";

        options.ResponseType = "code id_token";
        options.Scope.Add("api");
        options.SaveTokens = true;
    });

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.Authority = "https://localhost:4991";
        options.RequireHttpsMetadata = false;
        // name of the API resource
        options.Audience = "api";
    });

services.AddAuthorization(options =>
{
    var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(
        CookieAuthenticationDefaults.AuthenticationScheme,
        JwtBearerDefaults.AuthenticationScheme);
    defaultAuthorizationPolicyBuilder =
        defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
    options.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();
});
_

[〜#〜] edit [〜#〜]

これは認証されたユーザーに対しては機能しますが、ユーザーがまだログインしていない場合は401(無許可)を返します。

不正なユーザーがログインページにリダイレクトされるようにするには、スタートアップクラスのConfigureメソッドに次のコードを追加します。注:新しいミドルウェアを配置することが重要ですafterapp.UseAuthentication()を呼び出します。

_app.UseAuthentication();
app.Use(async (context, next) =>
{
    await next();
    var bearerAuth = context.Request.Headers["Authorization"]
        .FirstOrDefault()?.StartsWith("Bearer ") ?? false;
    if (context.Response.StatusCode == 401
        && !context.User.Identity.IsAuthenticated
        && !bearerAuth)
    {
        await context.ChallengeAsync("oidc");
    }
});
_

このリダイレクトを実現するよりクリーンな方法を知っている場合は、コメントを投稿してください!

3
David Kirkland