我们有一个带有API后端的SPA(Angular)(ASP.NET Core WebAPI):
SPA会在app.mydomain.com
上审核app.mydomain.com/API
我们使用JWT进行身份验证,内置Microsoft.AspNetCore.Authentication.JwtBearer
;我有一个控制器app.mydomain.com/API/auth/jwt/login
来创建令牌。 SPA将它们保存到本地存储中。一切都很完美。在进行安全审核之后,我们被告知要切换本地存储以获取cookie。
问题是,app.mydomain.com/API
上的API由SPA使用,但也由移动应用和多个客户服务器2服务器解决方案使用。
因此,我们必须按原样保留JWT,但添加Cookie。我找到了几篇将Cookies和JWT结合在不同控制器上的文章,但我需要它们在每个控制器上并排工作。
如果客户端发送cookie,请通过cookie进行身份验证。如果客户端发送JWT承载,则通过JWT进行身份验证。
这可以通过内置的ASP.NET身份验证或DIY中间件实现吗?
谢谢!
答案 0 :(得分:4)
我遇到了同样的问题,我只是在stackoverflow的另一个问题中找到了解决方案。
请查看this。
我将自己尝试该解决方案,并使用结果更新此答案。
编辑:似乎不可能以相同的方法实现双重身份验证类型,但是我提到的链接中提供的解决方案说:
不可能用两个Schemes Or-Like来授权一个方法,但是您可以使用两个公共方法来调用私有方法
<nav></nav> <section class="main"> <aside></aside> <section class="content">hello,programmer</section> </section>
无论如何,您应该查看链接,它肯定对我有所帮助。 归功于Nikolaus的答案。
答案 1 :(得分:3)
我认为最简单的解决方案是David Kirkland提出的解决方案:
创建组合授权策略(在ConfigureServices(IServiceCollection services)
中):
services.AddAuthorization(options =>
{
var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(
CookieAuthenticationDefaults.AuthenticationScheme,
JwtBearerDefaults.AuthenticationScheme);
defaultAuthorizationPolicyBuilder =
defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
options.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();
});
并添加中间件,该中间件在401(在Configure(IApplicationBuilder app)
中)的情况下将重定向到登录名:
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");
}
});
答案 2 :(得分:2)
我没有找到很多信息来找到实现此目的的好方法-仅为了支持2种授权方案而不得不复制API就是一种痛苦。
我一直在研究使用反向代理的想法,在我看来,这是一个很好的解决方案。
请注意,防伪令牌(#2,#4)非常重要,否则您可能会将API暴露给CSRF攻击。
示例(带有IdentityServer4的.NET Core 2.1 MVC):
要获得一个可行的示例,我从IdentityServer4快速入门Switching to Hybrid Flow and adding API Access back开始。这样就建立了我在MVC应用程序使用cookie并可以从身份服务器请求access_token进行API调用之后的场景。
我将Microsoft.AspNetCore.Proxy用于反向代理并修改了快速入门。
MVC Startup.ConfigureServices:
services.AddAntiforgery();
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
MVC启动。配置:
app.MapWhen(IsApiRequest, builder =>
{
builder.UseAntiforgeryTokens();
var messageHandler = new BearerTokenRequestHandler(builder.ApplicationServices);
var proxyOptions = new ProxyOptions
{
Scheme = "https",
Host = "api.mydomain.com",
Port = "443",
BackChannelMessageHandler = messageHandler
};
builder.RunProxy(proxyOptions);
});
private static bool IsApiRequest(HttpContext httpContext)
{
return httpContext.Request.Path.Value.StartsWith(@"/api/", StringComparison.OrdinalIgnoreCase);
}
ValidateAntiForgeryToken(Marius Schulz):
public class ValidateAntiForgeryTokenMiddleware
{
private readonly RequestDelegate next;
private readonly IAntiforgery antiforgery;
public ValidateAntiForgeryTokenMiddleware(RequestDelegate next, IAntiforgery antiforgery)
{
this.next = next;
this.antiforgery = antiforgery;
}
public async Task Invoke(HttpContext context)
{
await antiforgery.ValidateRequestAsync(context);
await next(context);
}
}
public static class ApplicationBuilderExtensions
{
public static IApplicationBuilder UseAntiforgeryTokens(this IApplicationBuilder app)
{
return app.UseMiddleware<ValidateAntiForgeryTokenMiddleware>();
}
}
BearerTokenRequestHandler:
public class BearerTokenRequestHandler : DelegatingHandler
{
private readonly IServiceProvider serviceProvider;
public BearerTokenRequestHandler(IServiceProvider serviceProvider, HttpMessageHandler innerHandler = null)
{
this.serviceProvider = serviceProvider;
InnerHandler = innerHandler ?? new HttpClientHandler();
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var httpContextAccessor = serviceProvider.GetService<IHttpContextAccessor>();
var accessToken = await httpContextAccessor.HttpContext.GetTokenAsync("access_token");
request.Headers.Authorization =new AuthenticationHeaderValue("Bearer", accessToken);
var result = await base.SendAsync(request, cancellationToken);
return result;
}
}
_Layout.cshtml:
@Html.AntiForgeryToken()
然后使用您的SPA框架可以发出请求。为了验证我只是做了一个简单的AJAX请求:
<a onclick="sendSecureAjaxRequest()">Do Secure AJAX Request</a>
<div id="ajax-content"></div>
<script language="javascript">
function sendSecureAjaxRequest(path) {
var myRequest = new XMLHttpRequest();
myRequest.open('GET', '/api/secureResource');
myRequest.setRequestHeader("RequestVerificationToken",
document.getElementsByName('__RequestVerificationToken')[0].value);
myRequest.onreadystatechange = function () {
if (myRequest.readyState === XMLHttpRequest.DONE) {
if (myRequest.status === 200) {
document.getElementById('ajax-content').innerHTML = myRequest.responseText;
} else {
alert('There was an error processing the AJAX request: ' + myRequest.status);
}
}
};
myRequest.send();
};
</script>
这是一个概念验证测试,因此您的工作量可能非常大,而且我对.NET Core和中间件配置还很陌生,因此可能看起来更漂亮。我对此进行了有限的测试,仅对API进行了GET请求,并且未使用SSL(https)。
如预期的那样,如果从AJAX请求中删除了防伪令牌,则它将失败。如果用户尚未登录(通过身份验证),则请求失败。
与往常一样,每个项目都是唯一的,因此请始终验证是否满足安全性要求。请查看此答案上留下的任何评论,以防有人可能提出任何潜在的安全问题。
另一方面,我认为一旦所有常用浏览器上都可以使用子资源完整性(SRI)和内容安全策略(CSP)(即淘汰了较旧的浏览器),则应重新评估本地存储以存储API令牌,这将降低令牌存储的复杂性。现在应使用SRI和CSP来减少支持浏览器的攻击面。
答案 3 :(得分:2)
好的,我已经尝试了一段时间,并且通过以下代码解决了使用jwt身份验证令牌和Cookie身份验证的相同问题。
API服务提供者 UserController.cs
这通过(Cookie和JWT承载)身份验证方案为用户提供不同的服务
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)]
[Route("[controller]")]
[ApiController]
public class UsersController : ControllerBase
{
private readonly IUserServices_Api _services;
public UsersController(IUserServices_Api services)
{
this._services = services;
}
[HttpGet]
public IEnumerable<User> Getall()
{
return _services.GetAll();
}
}
我的 Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddAuthentication(options => {
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.LoginPath = "/Account/Login";
options.AccessDeniedPath = "/Home/Error";
})
.AddJwtBearer(options =>
{
options.SaveToken = true;
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidAudience = " you site link blah blah",
ValidIssuer = "You Site link Blah blah",
IssuerSigningKey = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(sysController.GetSecurityKey()))
,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
});
}
此外,如果您想为特定控制器进行自定义身份验证 那么您必须为授权指定身份验证类型 喜欢:
[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)]
public IActionResult Index()
{
return View(); // This can only be Access when Cookie Authentication is Authorized.
}
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public IActionResult Index()
{
return View(); // And this one will be Access when JWT Bearer is Valid
}
答案 4 :(得分:1)
在寻找与 net core web api(网站的 cookie 和移动应用的授权标头)相结合的 firebase 授权时,以以下解决方案结束。
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://securetoken.google.com/xxxxx";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = options.Authority,
ValidateAudience = true,
ValidAudience = "xxxxx",
ValidateLifetime = true
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
if (context.Request.Cookies.ContainsKey(GlobalConst.JwtBearer))
{
context.Token = context.Request.Cookies[GlobalConst.JwtBearer];
}
else if (context.Request.Headers.ContainsKey("Authorization"))
{
var authhdr = context.Request.Headers["Authorization"].FirstOrDefault(k=>k.StartsWith(GlobalConst.JwtBearer));
if (!string.IsNullOrEmpty(authhdr))
{
var keyval = authhdr.Split(" ");
if (keyval != null && keyval.Length > 1) context.Token = keyval[1];
}
}
return Task.CompletedTask;
}
};
});
哪里
public static readonly string JwtBearer = "Bearer";
似乎工作正常。 从手机和邮递员那里检查过(用于 cookie)
答案 5 :(得分:-1)
ASP.NET Core 2.0 Web API
请按照此帖发布基于JWT令牌的身份验证
如果您正在使用visual studio,请确保将Bearer类型的身份验证类型应用于过滤器
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
用于控制器或操作。