ASP.Net Core MVC/API/SignalR - 更改身份验证方案(Cookie 和 JWT)

Tai*_*T's 4 c# authentication jwt asp.net-core-mvc asp.net-core-2.2

我有一个 .Net Core 2.2 Web 应用程序 MVC,我在其中添加了 API 控制器和 SignalR 集线器。另一方面,我有一个调用集线器方法的移动应用程序。在从应用程序调用集线器之前,我通过 API 调用对我的用户进行身份验证 - 取回 JWT 令牌 - 并将此令牌用于未来的请求,这样我就可以Context.User.Identity.Name在我的集线器方法中使用:

public static async Task<string> GetValidToken(string userName, string password)
{
   using (var client = new HttpClient())
   {
     client.BaseAddress = new Uri(_API_BASE_URI);
     client.DefaultRequestHeaders.Accept.Clear();
     client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

     LoginViewModel loginVM = new LoginViewModel() { Email = userName, Password = password, RememberMe = false };
     var formContent = Newtonsoft.Json.JsonConvert.SerializeObject(loginVM);
     var content = new StringContent(formContent, Encoding.UTF8, "application/json");
     HttpResponseMessage responseMessage;
     try
     {
        responseMessage = await client.PostAsync("/api/user/authenticate", content);
        var responseJson = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false); ;
        var jObject = JObject.Parse(responseJson);
        _TOKEN = jObject.GetValue("token").ToString();
        return _TOKEN;
     }catch
        [...]
Run Code Online (Sandbox Code Playgroud)

然后使用令牌:

_connection = new HubConnectionBuilder().WithUrl(ApiCommunication._API_BASE_URI + "/network", options =>
{
  options.AccessTokenProvider = () => Task.FromResult(token);
}).Build();
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好。它在我的移动应用程序上按预期工作。但是为了使它工作,我必须在服务器端(Startup.cs)设置这段代码:

services.AddAuthentication(options =>
{
   options .DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
   options .DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
 })
 .AddJwtBearer(x =>
 {
    x.Events = new JwtBearerEvents
    {
        OnMessageReceived = context =>
        {
            ...
Run Code Online (Sandbox Code Playgroud)

这阻止我再使用 cookie 身份验证,因此 mvc Web 应用程序不再按预期工作,因为它无法在请求中获取当前经过身份验证的用户。

删除行:

options .DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options .DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
Run Code Online (Sandbox Code Playgroud)

使网络应用程序正常工作,但不再是移动应用程序(由于Context.User.Identity.Name等于 null,集线器调用失败)。

我一直在四处寻找如何处理不同的方案(在我的情况下是 cookie + jwt),据我所知,这是设计使然不再可能。

是否有任何可能的解决方法来使用双重方案还是我遗漏了什么?

我想也许我应该托管 2 个单独的项目,一个使用 Cookie 身份验证,另一个使用 JWT?

提前致谢。

Mic*_*iey 5

有多种方法可以解决您遇到的问题,但首先让我们了解一下它目前不起作用的原因。

什么DefaultAuthenticateScheme意思

当您为 的DefaultAuthenticateScheme属性设置一个值时AuthenticationOptions,您指示身份验证中间件尝试根据该特定方案对每个 HTTP 请求进行身份验证。我将假设您使用 ASP.NET Identity 进行基于 cookie 的身份验证,并且当您调用 时AddIdentity,它会将 cookie 身份验证方案注册为用于身份验证的默认方案;您可以在 GitHub 上源代码中看到这一点。

但是,这并不意味着您不能在应用程序中使用任何其他身份验证方案。

授权系统默认策略

如果您的应用程序的所有受保护端点都可供使用 cookie 或 JWT 进行身份验证的客户端访问,则一种选择是使用授权系统默认策略。当您使用AuthorizeAttribute该类的“空”实例时,将使用该特殊策略- 作为装饰控制器/操作的属性,或在应用程序级别全局使用new AuthorizeFilter(new AuthorizeAttribute()).

默认策略设置为仅需要经过身份验证的用户,但未定义需要“尝试”哪些身份验证方案来对请求进行身份验证。结果是它依赖于已经执行的身份验证过程。它解释了您遇到的行为,其中一次只有两种方案中的一种有效。

我们可以使用一些代码更改默认策略:

services.AddAuthorization(options =>
{
    options.DefaultPolicy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .AddAuthenticationSchemes("<your-cookie-authentication-scheme", "your-jwt-authentication-scheme")
        .Build();
})
Run Code Online (Sandbox Code Playgroud)

具体授权政策

如果您发现自己需要某些端点只能由使用 cookie 进行身份验证的客户端访问,而其他端点可以通过 JWT 访问,您可以利用授权策略。

它们的工作方式与默认策略完全相同,希望您可以根据端点选择哪个适用。您可以添加如下策略:

services.AddAuthorization(options =>
{
    options.AddPolicy("Cookies", new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .AddAuthenticationSchemes("<your-cookie-authentication-scheme")
        .Build());

    options.AddPolicy("JWT", new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .AddAuthenticationSchemes("<your-jwt-authentication-scheme")
        .Build());
})
Run Code Online (Sandbox Code Playgroud)

然后,您可以通过用 装饰它们在适当的端点中引用这些策略[Authorize(Policy = "<policy-name>")]。附带说明一下,如果您的策略之间的唯一区别是身份验证方案,则可以在不创建策略的情况下实现相同的结果,并在[Authorize]属性中引用适当的身份验证方案AuthenticationSchemes

当您有更复杂的规则时,策略很有价值,例如特定声明需要此特定值

我希望这会有所帮助,让我知道你的去向!