ASP.NET Core WebAPI Cookie + JWT身份验证

Luk*_*988 6 c# authentication cookies jwt asp.net-core

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

SPA正在侦听app.mydomain.com,API 正在侦听app.mydomain.com/API

我们使用内置的JWT进行身份验证Microsoft.AspNetCore.Authentication.JwtBearer;我有一个app.mydomain.com/API/auth/jwt/login创建令牌的控制器。SPA将它们保存到本地存储中。所有作品都很完美。经过安全审核后,我们被告知要为cookie切换本地存储。

问题是app.mydomain.com/APISPA以及移动应用程序和一些客户的服务器2服务器解决方案都使用API on 。

因此,我们必须保持JWT不变,但要添加Cookies。我发现了几篇文章,它们结合了Cookie和JWT在不同的控制器上,但是我需要它们在每个控制器上并行工作。

如果客户端发送cookie,则通过cookie进行身份验证。如果客户端发送JWT承载,则通过JWT进行身份验证。

是否可以通过内置的ASP.NET身份验证或DIY中间件来实现?

谢谢!

S. *_*ees 8

好的,我一直在尝试实现这一目标,并且通过以下代码解决了使用 jwt 身份验证令牌和 Cookie 身份验证的相同问题。

API 服务提供者UserController.cs

这使用(Cookie 和 JWT Bearer)两种身份验证方案为用户提供不同的服务

[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();
    }
}
Run Code Online (Sandbox Code Playgroud)

我的启动.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
                };
            });

    }
Run Code Online (Sandbox Code Playgroud)

此外,如果您想为特定控制器自定义身份验证,则必须为授权指定身份验证类型,例如:

[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
}
Run Code Online (Sandbox Code Playgroud)


小智 7

我一直有同样的问题,我刚刚在 stackoverflow 的另一个问题中找到了它似乎是解决方案。

请看看这个

我会自己尝试该解决方案并使用结果更新此答案。

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

不可能使用两个 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();
}    
Run Code Online (Sandbox Code Playgroud)

无论如何,您应该查看链接,它确实对我有帮助。归功于尼古拉斯的答案。


Mik*_*ley 5

我无法找到有关执行此操作的好方法的很多信息 - 仅仅为了支持 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 快速入门切换到混合流并添加 API 访问开始。这设置了我使用的 MVC 应用程序使用 cookie 并可以从身份服务器请求 access_token 以调用 API 的场景。

我使用Microsoft.AspNetCore.Proxy作为反向代理并修改了快速启动。

MVC Startup.ConfigureServices:

services.AddAntiforgery();
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
Run Code Online (Sandbox Code Playgroud)

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);
}
Run Code Online (Sandbox Code Playgroud)

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>();
    }
}
Run Code Online (Sandbox Code Playgroud)

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

_Layout.cshtml:

@Html.AntiForgeryToken()
Run Code Online (Sandbox Code Playgroud)

然后使用您的 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>
Run Code Online (Sandbox Code Playgroud)

这是一个概念验证测试,所以你的里程可能非常多,而且我对 .NET Core 和中间件配置很陌生,所以它可能看起来更漂亮。我对此做了有限的测试,只对 API 发出了 GET 请求,没有使用 SSL (https)。

正如预期的那样,如果从 AJAX 请求中删除防伪令牌,它将失败。如果用户尚未登录(经过身份验证),则请求失败。

与往常一样,每个项目都是独一无二的,因此请始终验证您的安全要求是否得到满足。请查看对此答案留下的任何评论,以了解有人可能提出的任何潜在安全问题。

另一方面,我认为一旦子资源完整性 (SRI) 和内容安全策略 (CSP) 在所有常用浏览器上可用(即淘汰旧浏览器),就应该重新评估本地存储以存储 API 令牌,这将降低复杂性令牌存储。 现在应该使用 SRI 和 CSP 来帮助减少支持浏览器的攻击面。


Hos*_*deh 5

通过此代码,您可以同时使用 cookie 和 header。如果 cookie 为空,则自动检查标头。

在 AddJwtBearer 选项中添加此代码。

options.Events = new JwtBearerEvents
{
    OnMessageReceived = context =>
    {
        context.Token = context.Request.Cookies["Authorization"];
        return Task.CompletedTask;
    }
};
Run Code Online (Sandbox Code Playgroud)

完整用法是:

        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
            {
                options.RequireHttpsMetadata = false;
                options.SaveToken = false;
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = true,
                    ValidateAudience = true,
                    ValidAudience = Configuration["JwtToken:Audience"],
                    ValidIssuer = Configuration["JwtToken:Issuer"],
                    IssuerSigningKey =
                        new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtToken:Key"]))
                };
                options.Events = new JwtBearerEvents
                {
                    OnMessageReceived = context =>
                    {
                        context.Token = context.Request.Cookies["Authorization"];
                        return Task.CompletedTask;
                    }
                };
            });
Run Code Online (Sandbox Code Playgroud)

标头 => 授权:持有者您的令牌

或者

Cookie => Authorization=Your-Token //不要在Cookie中添加Bearer


Mar*_*eio -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,请确保应用带有过滤器的承载类型认证类型

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
Run Code Online (Sandbox Code Playgroud)

用于控制器或操作。

  • 虽然此链接可以回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接的答案可能会变得无效。 (2认同)