ASP.NET Core 2.0将Cookie和承载授权结合在一起,用于同一端点

max*_*ini 19 .net c# asp.net authentication asp.net-core-2.0

我在VS17中使用"Web应用程序(模型 - 视图 - 控制器)"模板和".Net Framework"+"ASP.NET Core 2"作为配置创建了一个新的ASP.NET核心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" } );
    }
}
Run Code Online (Sandbox Code Playgroud)

"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"]))
                                                };
        });
Run Code Online (Sandbox Code Playgroud)

因此,当我尝试使用浏览器访问端点时,我得到了一个带有空白html页面的401响应.
我得到了一个空白的html页面的401响应.

然后我登录,当我再次尝试访问端点时,我得到相同的响应.

然后,我尝试通过指定承载令牌来访问端点.并且返回200响应所需的结果.
并且返回200响应所需的结果.

那么,如果我删除[Authorize(AuthenticationSchemes = "Cookies,Bearer")],情况就会相反 - cookie身份验证工作并返回200,但是上面使用的相同的承载令牌方法不会给出任何结果,只是重定向到默认的AspIdentity登录页面.

我在这里可以看到两个可能的问题:

1)ASP.NET Core不允许"组合"身份验证.2)'Cookies'不是有效的模式名称.但那么正确的使用方法是什么?

请指教.谢谢.

Dav*_*and 20

如果我正确理解了这个问题,那么我相信有一个解决方案。在以下示例中,我在单个应用程序中使用 cookie 和不记名身份验证。该[Authorize]属性可以在不指定方案的情况下使用,应用程序将根据所使用的授权方法动态做出反应。

services.AddAuthentication被调用两次以注册 2 个身份验证方案。解决方案的关键services.AddAuthorization代码片段末尾的 调用,它告诉 ASP.NET 使用 BOTH 方案。

我已经对此进行了测试,它似乎运行良好。

(基于Microsoft 文档。)

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();
});
Run Code Online (Sandbox Code Playgroud)

编辑

这适用于经过身份验证的用户,但如果用户尚未登录,则仅返回 401(未经授权)。

为确保将未经授权的用户重定向到登录页面,请将以下代码添加到ConfigureStartup 类中的方法中。注:这是至关重要的是,新的中间件放置的呼叫app.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");
    }
});
Run Code Online (Sandbox Code Playgroud)

如果您知道实现此重定向的更简洁的方法,请发表评论!

  • 在 .Net 5+ 中调用“services.AddAuthentication”两次会导致仅使用最后一个配置,从而使上述代码无用。 (2认同)

Nik*_*aus 10

我认为您不需要将AuthenticationScheme设置为Controller.只需在ConfigureServices中使用Authenticated用户,如下所示:

// 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));
});
Run Code Online (Sandbox Code Playgroud)

有关我的资源的文档:registerAuthorizationHandlers

对于该部分,无论scheme-Key是否无效,您都可以使用插值字符串来使用正确的键:

[Authorize(AuthenticationSchemes = $"{CookieAuthenticationDefaults.AuthenticationScheme},{JwtBearerDefaults.AuthenticationScheme}")]
Run Code Online (Sandbox Code Playgroud)

编辑:我做了进一步的研究并得出以下结论:不可能使用两个Schemes Or-Like授权方法,但是你可以使用两个公共方法来调用这样的私有方法:

//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();
}
Run Code Online (Sandbox Code Playgroud)


Chr*_*ens 10

经过数小时的研究和头疼,这就是在 ASP.NET Core 2.2 -> ASP.NET 5.0 中对我有用的方法:

  • 使用 .AddCookie() 和 .AddJwtBearer() 来配置方案
  • 使用自定义策略方案转发到正确的身份验证方案。

您不需要在每个控制器操作上指定方案,并且适用于两者。【授权】就够了。

services.AddAuthentication( config =>
{
    config.DefaultScheme = "smart";
} )
.AddPolicyScheme( "smart", "Bearer or Jwt", options =>
{
    options.ForwardDefaultSelector = context =>
    {
        var bearerAuth = context.Request.Headers["Authorization"].FirstOrDefault()?.StartsWith( "Bearer " ) ?? false;
        // You could also check for the actual path here if that's your requirement:
        // eg: if (context.HttpContext.Request.Path.StartsWithSegments("/api", StringComparison.InvariantCulture))
        if ( bearerAuth )
            return JwtBearerDefaults.AuthenticationScheme;
        else
            return CookieAuthenticationDefaults.AuthenticationScheme;
    };
} )
.AddCookie( CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
    options.LoginPath = new PathString( "/Account/Login" );
    options.AccessDeniedPath = new PathString( "/Account/Login" );
    options.LogoutPath = new PathString( "/Account/Logout" );
    options.Cookie.Name = "CustomerPortal.Identity";
    options.SlidingExpiration = true;
    options.ExpireTimeSpan = TimeSpan.FromDays( 1 ); //Account.Login overrides this default value
} )
.AddJwtBearer( JwtBearerDefaults.AuthenticationScheme, options =>
{
    options.RequireHttpsMetadata = false;
    options.SaveToken = true;
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey( key ),
        ValidateIssuer = false,
        ValidateAudience = false
    };
} );

services.AddAuthorization( options =>
{
    options.DefaultPolicy = new AuthorizationPolicyBuilder( CookieAuthenticationDefaults.AuthenticationScheme, JwtBearerDefaults.AuthenticationScheme )
        .RequireAuthenticatedUser()
        .Build();
} );
Run Code Online (Sandbox Code Playgroud)

  • 感谢您的大力帮助。我尝试过此解决方案正在使用 JWT + Cookie 身份验证、Cookie 来验证应用程序页面以及 JWT 来验证不记名令牌基础 API 调用。关键选择是 AddPolicyScheme() 的工作方式。 (2认同)