HttpClient 在 Blazor Web assembly 应用程序中的请求中不包含 cookie

Dum*_*DED 10 c# cookies dotnet-httpclient asp.net-core blazor-webassembly

我有一个 Blazor Web assembly 应用程序,其中包含一个用户服务,旨在访问 API 来检索用户的详细信息。该服务如下所示:

public class UserDataService : IUserDataService
{
    public readonly HttpClient _HttpClient;

    public UserDataService(HttpClient httpClientDI)
    {
        _HttpClient = httpClientDI;
    }

    public async Task<User> GetUserInfo()
    {
        try
        {
            return await _HttpClient.GetFromJsonAsync<User>("api/users/MyUserInfo");
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            throw;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

该 API 专门设计用于从客户端请求中读取加密的 cookie。该 cookie 包含用户的电子邮件地址,用户信息服务使用它来检索更详细的用户信息集。

[HttpGet("MyUserInfo")]
public User MyUserInfo()
{
    var myCookie = HttpContext.Request.Cookies.FirstOrDefault(c => c.Key == "MyCookie");

    var userMask = JsonConvert.DeserializeObject<AuthUserMask>(Protector.Unprotect(myCookie.Value));

    var user = UserService.Find(userMask.Email).FirstOrDefault();

    return user;
}
Run Code Online (Sandbox Code Playgroud)

当我运行 Web 应用程序时,我能够验证浏览器中是否存在 cookie,但是当应用程序向 API 发出请求时,不包含 cookie。事实上,该请求根本不包含来自客户端的任何 cookie。

在此输入图像描述

我对 Blazor 完全陌生,我不确定这种情况是否存在任何约定,但目前我只是想让这个新的 Web 应用程序与我们现有的服务一起使用。有没有办法确保包含 cookie?我可能做错了什么?

先谢谢您的帮助。

编辑

这是创建 cookie 的代码。它是验证用户是否经过身份验证的更大方法的一部分,但这是相关部分:

{
    var userJson = JsonConvert.SerializeObject(new AuthUserMask()
    {
        Email = user.Email,
        isActive = user.IsActive
    });

    var protectedContents = Protector.Protect(userJson);

    HttpContext.Response.Cookies.Append("MyCookie", protectedContents, new CookieOptions()
    {
        SameSite = SameSiteMode.None,
        Secure = true,
        Path = "/",
        Expires = DateTime.Now.AddMinutes(60)
    });

    HttpContext.Response.Redirect(returnUrl);
}
Run Code Online (Sandbox Code Playgroud)

编辑2

在 UserDataService 中尝试以下操作,看看会发生什么:

public async Task<User> GetUserInfo()
{
    try
    {
        _HttpClient.DefaultRequestHeaders.Add("Test", "ABC123");
        return await _HttpClient.GetFromJsonAsync<User>("api/users/MyUserInfo");
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        throw;
    }
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,结果是一样的——当 RequestCookieCollection 访问 API 时,它完全是空的。

Nic*_*ada 5

在 Program.cs 中使用 Blazor .net 6 样式,您需要以下代码:

builder.Services
    .AddTransient<CookieHandler>()
    .AddScoped(sp => sp
        .GetRequiredService<IHttpClientFactory>()
        .CreateClient("API"))
    .AddHttpClient("API", client => client.BaseAddress = new Uri(apiAddress)).AddHttpMessageHandler<CookieHandler>();
Run Code Online (Sandbox Code Playgroud)

那么你需要 @murat_yuceer 描述的处理程序,例如:

namespace Client.Extensions
{
    public class CookieHandler : DelegatingHandler
    {
        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            request.SetBrowserRequestCredentials(BrowserRequestCredentials.Include);

            return await base.SendAsync(request, cancellationToken);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

您不需要(也不应该)指定 cookie。正确的 cookie 将会发送给您,只需BrowserRequestCredentials.Include在消息中添加即可。

在拥有 API 的服务器端,您需要设置 CORS 允许凭据。

使用 .net 6 语法,您应该已经在Program.cs中使用:

app.UseCors(x => x.
  .AllowAnyHeader()
  .AllowAnyMethod()
  .AllowAnyOrigin()
);
Run Code Online (Sandbox Code Playgroud)

但你还需要AllowCredentials()

如果添加,AllowCredentials您将收到以下运行时错误:

System.InvalidOperationException: 'The CORS protocol does not allow specifying a wildcard (any) origin and credentials at the same time. Configure the CORS policy by listing individual origins if credentials needs to be supported.'
Run Code Online (Sandbox Code Playgroud)

因此,您需要指定允许的来源,或像这样的通配符:

System.InvalidOperationException: 'The CORS protocol does not allow specifying a wildcard (any) origin and credentials at the same time. Configure the CORS policy by listing individual origins if credentials needs to be supported.'
Run Code Online (Sandbox Code Playgroud)

现在一切都应该按预期进行。


Dum*_*DED 1

根据 @Mihaimyh 的一些见解,我能够使用用户数据服务上的自定义委托处理程序来使其工作。它是这样注册的:

builder.Services.AddHttpClient<IUserDataService, UserDataService>(client => client.BaseAddress = new Uri("https://localhost:44336/"))
                .AddHttpMessageHandler<CustomDelegatingHandler>();
Run Code Online (Sandbox Code Playgroud)

在内部,它用于JSInterop运行 Javascript 函数来检索 cookie,然后将其附加到使用该SendAsync()方法的所有传出请求:

public class CustomDelegatingHandler : DelegatingHandler
{
    private IJSRuntime JSRuntime;

    public CustomDelegatingHandler(IJSRuntime jSRuntime) : base()
    {
        JSRuntime = jSRuntime;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var cookie = await JSRuntime.InvokeAsync<string>("blazorExtensions.GetCookie", new[] { "MyCookie" });
        Debug.WriteLine($"My cookie: {cookie}");
        request.Headers.Add("MyCookie", $"{cookie}");
        return await base.SendAsync(request, cancellationToken);
    }
}
Run Code Online (Sandbox Code Playgroud)

Javascript 函数如下所示(几乎逐字摘自W3Schools):

window.blazorExtensions = { 
    GetCookie: function (cname) {
        var name = cname + "=";
        var decodedCookie = decodeURIComponent(document.cookie);
        var ca = decodedCookie.split(';');
        for (var i = 0; i < ca.length; i++) {
            var c = ca[i];
            while (c.charAt(0) == ' ') {
                c = c.substring(1);
            }
            if (c.indexOf(name) == 0) {
                return c.substring(name.length, c.length);
            }
        }
        return "";
    }
}
Run Code Online (Sandbox Code Playgroud)

我还在服务端进行了修改,以在标头中查找 cookie,而不是在 cookie 集合中查找。现在,代替这个...

var myCookie = HttpContext.Request.Cookies.FirstOrDefault(c => c.Key == "MyCookie");
Run Code Online (Sandbox Code Playgroud)

...我已经这样做了:

HttpContext.Request.Headers.TryGetValue("MyCookie", out var myCookie);
Run Code Online (Sandbox Code Playgroud)

诚然,我不知道这如何符合 Blazor 应用程序中此类事物的约定,但它似乎足以满足我们的目的。再次感谢大家的帮助。