首页 文章

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

提问于
浏览
1

我们有一个带有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 回答

  • 0

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

    请看一下this .

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

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

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

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

  • 1

    我无法找到关于这样做的好方法的大量信息 - 不得不复制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来帮助减少支持浏览器的攻击面 .

  • 2

    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)]
    

    用于控制器或动作 .

相关问题