ASP.NET核心WebAPI Cookie JWT身份验证

我们有一个带有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中间件实现吗?

谢谢!

回答(3)

3 years ago

我一直有同样的问题,我刚刚在stackoverflow中找到了另一个问题中的解决方案 .

请看一下this .

我将自己尝试该解决方案,并用结果更新此答案 .

编辑:似乎不可能在同一方法中实现双重身份验证类型,但我提到的链接中提供的解决方案说:

无法使用两个Schemes Or-Like授权方法,但是您可以使用两个公共方法来调用私有方法//私有方法
private IActionResult GetThingPrivate()
{
//你的代码在这里
}
//智威汤逊法
[授权(AuthenticationSchemes = $“”)]
[HTTPGET( “承载”)]
public IActionResult GetByBearer()
{
return GetThingsPrivate();
}
// Cookie的方法
[授权(AuthenticationSchemes = $“”)]
[HTTPGET( “曲奇”)]
public IActionResult GetByCookie()
{
return GetThingsPrivate();
}

无论如何你应该看看链接,它肯定帮助了我 . 归功于Nikolaus的答案 .

3 years ago

我无法找到关于这样做的好方法的大量信息 - 不得不复制API只是为了支持2个授权方案 .

我一直在研究使用反向代理的想法,它看起来像是一个很好的解决方案 .

  • 用户登录网站(使用cookie httpOnly进行会话)

  • 网站使用Anti-Forgery令牌

  • SPA向网站服务器发送请求,并在标头中包含防伪标记:https://app.mydomain.com/api/secureResource

  • 网站服务器验证防伪令牌(CSRF)

  • 网站服务器确定请求是针对API的,并应将其发送到反向代理

  • 网站服务器获取API的用户访问令牌

  • 反向代理将请求转发给API:https://api.mydomain.com/api/secureResource

Note that the anti-forgery token (#2,#4) is critical or else you could expose your API to CSRF attacks.


Example (.NET Core 2.1 MVC with IdentityServer4):

为了得到一个有效的例子,我开始使用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 Startup.Configure:

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 years ago

ASP.NET Core 2.0 Web API

请按照此帖子实施基于 JWT Token 的身份验证

https://fullstackmark.com/post/13/jwt-authentication-with-aspnet-core-2-web-api-angular-5-net-core-identity-and-facebook-login

如果您使用的是visual studio,请确保将Bearer类型的身份验证类型与过滤器一起使用

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]

用于控制器或动作 .