HttpClient 在 Blazor Webassembly 应用程序中不包含带有请求的 cookie
Posted
技术标签:
【中文标题】HttpClient 在 Blazor Webassembly 应用程序中不包含带有请求的 cookie【英文标题】:HttpClient doesn't include cookies with requests in Blazor Webassembly app 【发布时间】:2020-09-10 14:31:37 【问题描述】:我有一个带有用户服务的 Blazor Webassembly 应用程序,该服务旨在通过 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;
API 专门设计用于从客户端请求中读取加密的 cookie。此 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;
当我运行 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);
编辑 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;
不幸的是,结果是一样的 - RequestCookieCollection 在访问 API 时是完全空的。
【问题讨论】:
能否也包括写入或配置cookie的代码?某些设置可能会影响浏览器将其正确包含在您的请求中的能力。 看起来将您的路径设置为“/”可能是问题所在。查看文档,如果请求是针对已发布域的根目录,这听起来只会包含您的 cookie。您介意在 CookieOptions 中删除该属性并查看问题是否仍然存在吗? docs.microsoft.com/en-us/dotnet/api/… 如果我没看错,您正试图将此 cookie 发送到服务器,在这种情况下,它不应该是Request
而不是 Response
?可以试试加httpclient.DefaultRequestHeaders.Add("MyCookie", "SomeCokkieContent");
情节变厚了。唯一引起我注意的是 Secure = true。当设置为 true 时,UI 和 API 都必须使用 HTTPS 进行所有通信。你也可以仔细检查一下。如果这不起作用,我赞成提请注意这个问题。祝你好运。
我在这里有点困惑,当您向服务器发布内容时,您试图在标头中包含某些内容,或者您正在寻找服务器在响应标头中发送的内容? _HttpClient.DefaultRequestHeaders.Add("Test", "ABC123");
后面应该跟一个 POST 请求,而不是 GET,如果您的范围是将它发送到服务器。
【参考方案1】:
这是我在测试 Blazor WebAssembly AspNet 托管应用程序中所做的:
FetchData.razor
@page "/fetchdata"
@using BlazorApp3.Shared
@inject HttpClient Http
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from the server.</p>
@if (forecasts == null)
<p><em>Loading...</em></p>
else
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
</tbody>
</table>
@code
private WeatherForecast[] forecasts;
protected override async Task OnInitializedAsync()
Http.DefaultRequestHeaders.Add("key", "someValue");
forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
通知Http.DefaultRequestHeaders.Add("key", "someValue");
在服务器端,WeatherForecastController
我正在查看密钥的请求标头,如果存在,我正在尝试获取值:
using BlazorApp3.Shared;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
namespace BlazorApp3.Server.Controllers
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
// The Web API will only accept tokens 1) for users, and 2) having the access_as_user scope for this API
private static readonly string[] scopeRequiredByApi = new string[] "user_impersonation" ;
private static readonly string[] Summaries = new[]
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
;
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
_logger = logger;
[HttpGet]
public IEnumerable<WeatherForecast> Get()
if (HttpContext.Request.Headers.ContainsKey("key"))
var success = HttpContext.Request.Headers.TryGetValue("key", out var headervalue);
if (success)
_logger.LogInformation(headervalue.ToString());
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
)
.ToArray();
我能够获取 http 请求标头上的值。
如果你需要创建一个cookie,你必须使用JsInterop
,更多细节在这里How do I create a cookie client side using blazor。
【讨论】:
我实际上已经在我的代码中尝试过了,它确实有效,至少我能够在标题中包含 cookie 值。话虽如此,HttpContext.Request.Cookies
集合仍然是空的。我也尝试过使用Http.DefaultRequestHeaders.Add("Set-Cookie", $"myCookie=someValue")
,但这仍然没有填充集合。我不知道如果我仍然可以从标头中读取 cookie 值有多重要,但这仍然令人费解。【参考方案2】:
基于@Mihaimyh 的一些见解,我能够使用用户数据服务上的自定义委派处理程序来实现这一点。它是这样注册的:
builder.Services.AddHttpClient<IUserDataService, UserDataService>(client => client.BaseAddress = new Uri("https://localhost:44336/"))
.AddHttpMessageHandler<CustomDelegatingHandler>();
在内部,它使用 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);
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 "";
我还修改了服务端的内容,以便在标头而不是 cookie 集合中查找 cookie。现在,而不是这个......
var myCookie = HttpContext.Request.Cookies.FirstOrDefault(c => c.Key == "MyCookie");
...我已经这样做了:
HttpContext.Request.Headers.TryGetValue("MyCookie", out var myCookie);
诚然,我不知道这如何与 Blazor 应用程序中的此类内容的约定保持一致,但它似乎足以满足我们的目的。再次感谢大家的帮助。
【讨论】:
可能很明显,但请记住注册您的 CustomDelegatingHandler builder.Services.AddScoped添加这个
public class CookieHandler : DelegatingHandler
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
request.SetBrowserRequestCredentials(BrowserRequestCredentials.Include);
return await base.SendAsync(request, cancellationToken);
【讨论】:
请确保添加一些关于代码在做什么以及为什么要使用它的信息【参考方案4】:在 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>();
那么您需要@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);
您不需要(也不应该)指定 cookie。
系统会为您发送正确的 cookie,只需在消息中添加 BrowserRequestCredentials.Include
。
在您拥有 API 的服务器端,您需要设置 CORS 允许凭据。
使用 Program.cs 中应该已有的 .net 6 语法:
app.UseCors(x => x.
.AllowAnyHeader()
.AllowAnyMethod()
.AllowAnyOrigin()
);
但你还需要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.'
所以你需要指定允许的来源,或者像这样的通配符:
app.UseCors(x => x
.AllowAnyHeader()
.AllowAnyMethod()
//.AllowAnyOrigin()
.SetIsOriginAllowed(origin => true)
.AllowCredentials()
);
现在一切都应该按预期工作了。
【讨论】:
以上是关于HttpClient 在 Blazor Webassembly 应用程序中不包含带有请求的 cookie的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Blazor WASM 客户端中访问 Httpclient 标头
HttpClient 在 Blazor Webassembly 应用程序中不包含带有请求的 cookie
尽管 Fiddler 显示收到了正确的响应,但 Blazor WASM 上的 HttpClient.SendAsync 返回空 HttpResponseMessage