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

时间:2018-03-23 18:08:20

标签: c# authentication cookies asp.net-core jwt

我们有一个带有API后端的SPA(Angular)(ASP.NET Core WebAPI):

SPA会在app.mydomain.com上审核app.mydomain.com/API

上的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中间件实现吗?

谢谢!

6 个答案:

答案 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就是一种痛苦。

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

  1. 用户登录网站(使用Cookie httpOnly进行会话)
  2. 网站使用防伪令牌
  3. SPA向网站服务器发送请求,并在标头https://app.mydomain.com/api/secureResource中包含防伪令牌
  4. 网站服务器验证防伪令牌(CSRF)
  5. 网站服务器确定请求是针对API的,应将其发送给反向代理
  6. 网站服务器为用户提供API的访问令牌
  7. 反向代理将请求转发到API:https://api.mydomain.com/api/secureResource

请注意,防伪令牌(#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令牌的身份验证

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

用于控制器或操作。