ASP.NET Core 2.0 为同一端点结合了 Cookie 和承载授权
Posted
技术标签:
【中文标题】ASP.NET Core 2.0 为同一端点结合了 Cookie 和承载授权【英文标题】:ASP.NET Core 2.0 combining Cookies and Bearer Authorization for the same endpoint 【发布时间】:2018-04-06 21:17:53 【问题描述】:我在 VS17 中使用“Web 应用程序(模型-视图-控制器)”模板和“.Net Framework”+“ASP.NET Core 2”作为配置创建了一个新的 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"]))
;
);
因此,当我尝试使用浏览器访问端点时,我得到了 401 响应和空白 html 页面。
然后我登录,当我再次尝试访问端点时,我得到了相同的响应。
然后,我尝试通过指定承载令牌来访问端点。这将返回 200 响应的所需结果。
那么,如果我删除 [Authorize(AuthenticationSchemes = "Cookies,Bearer")]
,情况就相反了——cookie 身份验证有效并返回 200,但是与上面使用的相同的不记名令牌方法没有给出任何结果,只是重定向到默认的 AspIdentity 登录页面.
我可以在这里看到两个可能的问题:
1) ASP.NET Core 不允许“组合”身份验证。 2) 'Cookies' 不是有效的模式名称。但是,什么是正确的使用呢?
请指教。谢谢。
【问题讨论】:
你使用Idendity吗? 我在 aspnet core 1.0 中使用相同的 cookie 和 Bearer。迁移到 2.0 我会遇到同样的问题 :( 如果我们在操作中完全不必提及AuthenticationScheme
,那就太好了。
是的@Nikolaus,我有以下身份设置:services.AddIdentity<ApplicationUser, IdentityRole>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders();
【参考方案1】:
如果我正确理解了这个问题,那么我相信有一个解决方案。在以下示例中,我在单个应用程序中使用 cookie 和不记名身份验证。 [Authorize]
属性可以在不指定方案的情况下使用,并且应用程序将根据所使用的授权方法动态地做出反应。
services.AddAuthentication
被调用两次以注册 2 个身份验证方案。 解决方案的关键是在代码 sn-p 的末尾调用services.AddAuthorization
,它告诉 ASP.NET 使用 BOTH 方案。
我已经对此进行了测试,它似乎运行良好。
(基于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();
);
编辑
这适用于经过身份验证的用户,但如果用户尚未登录,则仅返回 401(未经授权)。
为确保将未经授权的用户重定向到登录页面,请将以下代码添加到 Startup 类中的 Configure
方法中。注意:必须将新中间件放置在 调用 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");
);
如果您知道实现此重定向的更简洁的方法,请发表评论!
【讨论】:
你确定这也适用于 asp.net Core 2.0 吗?看来,这针对的是 3.x 版本,不是吗? 什么面向 ASP.NET Core 3?我在回答时正在测试的版本是 Core 2.0。 那我要道歉了。AddOpenIdConnect
是 Identity Server 的一部分,真的需要一个才能让 Web 登录正常工作吗?这是将 JWT 令牌存储在 cookie 中的唯一方法吗?我问是因为你不能只添加Microsoft.AspNetCore.Authentication.OpenIdConnect
,它需要Identity Server提供的一些众所周知的URI ...
@Mike 试试看。去年,我正在处理文档、博客文章等,但如果您可以使用更标准、开箱即用的方法使其工作,那么请发布新答案。【参考方案2】:
我认为您不需要将 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")]
编辑: 我做了进一步的研究,得出以下结论: 无法使用两个 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();
【讨论】:
感谢您的回复!不幸的是,这并不能解决问题:如果我使用您评论中的配置 sn-p,并从端点装饰器中删除 AuthenticationScheme,则标准 cookie 方法有效,但令牌方法无效。 @maximiniini 您是否尝试颠倒顺序?喜欢:[Authorize(AuthenticationSchemes = "Bearer,Cookies")]
@maximiniini 我更新了我的答案。也许这可以帮助你。
谢谢@Nikolaus。我认为这是我将用于我的项目的解决方案。【参考方案3】:
经过数小时的研究和摸不着头脑,这就是在 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();
);
【讨论】:
如何使用访问令牌创建 cookie - 与本地存储不记名令牌相同?为什么我需要这个,因为我制作了window.open
并且需要访问已登录的用户身份。我无法修改标头,因此只有 URL(坏主意)和 cookie 可用于读取令牌...如果我添加 AddCookie - 登录后 Cookies 中没有任何内容。本地存储照常填满... AspNetCore 3.1
感谢您的大力帮助。我试过这个解决方案是使用 JWT + Cookie auth、Cookies 来验证应用程序页面和 JWT 来验证 Bearer 令牌基础 API 调用。关键选择是 AddPolicyScheme() 的工作方式。【参考方案4】:
使用 Asp.net Core 2.2 测试
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
)
.AddJwtBearer(options =>
options.Authority = "https://localhost:4991";
options.RequireHttpsMetadata = false;
// name of the API resource
options.Audience = "api";
);
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.AddAuthorization(options =>
// Add policies for API scope claims
options.AddPolicy(AuthorizationConsts.ReadPolicy,
policy => policy.RequireAssertion(context =>
context.User.HasClaim(c =>
((c.Type == AuthorizationConsts.ScopeClaimType && c.Value == AuthorizationConsts.ReadScope)
|| (c.Type == AuthorizationConsts.IdentityProviderClaimType))) && context.User.Identity.IsAuthenticated
));
// No need to add default policy here
);
app.UseAuthentication();
app.UseCookiePolicy();
在控制器中,添加必要的Authorize属性
[Authorize(AuthenticationSchemes = AuthorizationConsts.BearerOrCookiesAuthenticationScheme, Policy = AuthorizationConsts.ReadPolicy)]
这里是辅助类
public class AuthorizationConsts
public const string BearerOrCookiesAuthenticationScheme = CookieAuthenticationDefaults.AuthenticationScheme + "," + IdentityServerAuthenticationDefaults.AuthenticationScheme;
public const string IdentityProviderClaimType = "idp";
public const string ScopeClaimType = "scope";
public const string ReadPolicy = "RequireReadPolicy";
public const string ReadScope = "data:read";
【讨论】:
【参考方案5】:我有一个场景,我需要仅将 Bearer 或 Cookie 用于文件下载 api。所以以下解决方案对我有用。
如下图配置服务。
services.AddAuthentication(options =>
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
)
.AddCookie()
.AddJwtBearer(options =>
options.Authority = gatewayUrl;
)
.AddOpenIdConnect(options =>
// Setting default signin scheme for openidconnect makes it to force
// use cookies handler for signin
// because jwthandler doesnt have SigninAsync implemented
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = "https://youridp.com";
options.ClientId = "yourclientid";
options.CallbackPath = "/signin-oidc";
options.ResponseType = OpenIdConnectResponseType.Code;
);
然后如下所示配置你的控制器。
[HttpGet]
[Authorize(AuthenticationSchemes = "Bearer,OpenIdConnect")]
public async Task<IActionResult> Download([FromQuery(Name = "token")] string token)
///your code goes here.
///My file download api will work with both bearer or automatically authenticate with cookies using OpenidConnect.
【讨论】:
【参考方案6】:Christo Carstens,答案对我来说非常有效。 只是想我会分享我添加到他的 AddPolicyScheme 的额外检查。 (往上看) 就我而言,问题是我有一个 Azure Web 服务,它使用 JWT 处理我的所有移动应用程序请求,但我还需要它作为使用 cookie 的 Google/Apple/Facebook 身份验证的网关。 我按照建议更新了我的创业公司
.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;
;
)
我唯一的问题是,如果对我的任何设置了 [Authorize] 属性的 api 调用进行了调用,并且标头中没有“授权”键,那么它将使用 Cookie 授权并返回未找到(404) 而不是未经授权的 (401)。 他检查路径的建议奏效了,但我想在将来可能没有该路径的任何方法上强制执行 JWT。 最后我选择了这段代码。
.AddPolicyScheme("CookieOrJWT", "Bearer or Jwt", options =>
options.ForwardDefaultSelector = context =>
var bearerAuth = context.Request.Headers["Authorization"].FirstOrDefault()?.StartsWith("Bearer ") ?? false;
if (bearerAuth)
return JwtBearerDefaults.AuthenticationScheme;
else
var ep = context.GetEndpoint();
var requiresAuth = ep?.Metadata?.GetMetadata<AuthorizeAttribute>();
return requiresAuth != null
? JwtBearerDefaults.AuthenticationScheme
: CookieAuthenticationDefaults.AuthenticationScheme;
;
)
通过检查端点元数据(仅在授权不在标头中的极少数情况下),我可以为任何用 [Authorize] 属性修饰的方法设置 JwtBearerDefaults.AuthenticationScheme。 即使该方法从其类继承 [Authorize] 属性并且没有显式设置它,这也有效。 例如
[ApiController]
[Route("api/[Controller]")]
[Authorize]
public class MyController : ControllerBase
[HttpGet]
public ActionResult MyWebRequestThatRequiresAuthorization()
return true;
感谢 Christo Carstens 提供的解决方案。我为此头破血流。为我节省了无数小时。
【讨论】:
以上是关于ASP.NET Core 2.0 为同一端点结合了 Cookie 和承载授权的主要内容,如果未能解决你的问题,请参考以下文章
将 JWT Bearer Authentication Web API 与 Asp.Net Core 2.0 结合使用的问题
同一站点中的 asp net core 2.0 JWT 和 Openid Connect 身份验证
asp.net core 2.0 DI将多个IInterface类注入控制器