无法连接到 Blazor webassembly 中的 SignalR
Failed to connect to SignalR in Blazor webassembly
我正在尝试从我的 blazor webassembly 客户端连接到 SignalR 服务,但我认为这在 CORS 上失败了。这是我的剃刀文件中的代码。
m_connection = new HubConnectionBuilder()
.WithUrl(myMircoServiceUrl, options =>
{
options.AccessTokenProvider = () => Task.FromResult(userService.Token);
})
.WithAutomaticReconnect()
.Build();
await m_connection.StartAsync();
然后在 webassembly 日志记录中我看到以下错误:
从源“http://localhost:5010”获取 'xxxx/negotiate?negotiateVersion=1' 的访问已被 CORS 策略阻止:对预检请求的响应未通过访问控制检查:否 'Access-Control-Allow-Origin' header 出现在请求的资源上。如果不透明响应满足您的需求,请将请求的模式设置为 'no-cors' 以在禁用 CORS 的情况下获取资源。
我在 Blazor 服务器配置中添加了以下 CORS 策略,并在微服务配置中添加了类似内容:
app.UseResponseCompression();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBlazorDebugging();
}
else
{
app.UseExceptionHandler(@"/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseAuthorization();
app.UseCors(policy => policy
.WithOrigins("http://localhost:5010")
.AllowAnyHeader()
.AllowAnyMethod());
app.UseClientSideBlazorFiles<Client.Program>();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapFallbackToClientSideBlazor<Client.Program>(@"index.html");
});
有人知道哪里出了问题吗?
更新 1
我现在在 Chrome 控制台中看到以下错误:
dotnet.js:1 到 'ws://localhost:5000/hubs/posts?id=9Jxs0DhP924zgw_eIeE9Lg' 的 WebSocket 连接失败:HTTP 身份验证失败;没有可用的有效凭据
更新 2
我从 SignalR 集线器中删除了 [Authorize]
属性,现在它可以连接了。我可以向集线器发送消息。问题是这个属性是有原因的,因为我不希望人们可以订阅不适合他们的消息
更新 3
仍然没有进展。考虑使用 IdentityServer4 将身份验证提取到单独的微服务。最后状态是我有以下启动例程:
- 微服务:gist.github.com/njannink/15595b77ffe1c0593be1a555fa37f83f
- Blazor 服务器:gist.github.com/njannink/7302a888110e24d199ea45b66da4f26b
- Blazor 客户端:gist.github.com/njannink/add2568cbf48c8b3c070ccd4f28fd127
就我而言,ASP.NET Core 2.2 我有一个 API,我希望能够使用 API 中的 SignalR 连接到我的客户端应用程序。
我有
的项目
- 网络API
- IdentityServer4
- MVC 客户端
以ASP.NET核心身份作为用户管理
为了让您的用户通过身份验证,您需要像这样实现一个 IUserIdProvider
public class IdBasedUserIdProvider : IUserIdProvider
{
public string GetUserId(HubConnectionContext connection)
{
//TODO: Implement USERID Mapper Here
//throw new NotImplementedException();
//return whatever you want to map/identify the user by here. Either ID/Email
return connection.User.FindFirst("sub").Value;
}
}
有了这个,我确保我正在将 ID/Email 推送到我从服务器或客户端调用的方法。虽然我总是可以在 HubContext 上使用 .User 并且它工作正常。
在我的网站 API Startup.cs 文件中我想出了
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(cfg =>
{
cfg.AddDefaultPolicy(policy =>
{
policy.WithOrigins(Configuration.GetSection("AuthServer:DomainBaseUrl").Get<string[]>())
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()
.SetIsOriginAllowed((_) => true)
.SetIsOriginAllowedToAllowWildcardSubdomains();
});
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, UserManager<AppUser> userManager,
RoleManager<IdentityRole> roleManager){
app.UseCors();
}
注意
Configuration.GetSection("AuthServer:DomainBaseUrl").Get() 从配置文件中检索允许 CORS 的域列表。
我在 My Client App COnfigureService Method 中做了这个配置
services.AddCors(cfg =>
{
cfg.AddDefaultPolicy(policy => {
policy.AllowAnyHeader();
policy.AllowAnyMethod();
policy.SetIsOriginAllowed((host) => true);
policy.AllowAnyOrigin();
});
});
希望对您的情况有所帮助。
最好的解决方案确实如 Ismail Umer 所描述的那样,使用类似 IdentityServer4 的独立身份验证服务。并在所有其他服务中使用此服务。这是我将在下一次迭代中做的事情。
作为短期解决方案,我临时将 blazor 服务器部分移到了我的 api 服务中,并使用双重身份验证方法(JWT header 或 cookie)。
var key = Encoding.UTF8.GetBytes(m_configuration[@"SecurityKey"]);
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = @"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier",
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true
};
})
.AddCookie();
// TODO: For time being support dual authorization. At later stage split in various micro-services and use IdentityServer4 for Auth
services.AddAuthorization(options =>
{
var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(
CookieAuthenticationDefaults.AuthenticationScheme,
JwtBearerDefaults.AuthenticationScheme);
defaultAuthorizationPolicyBuilder =
defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
options.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();
});
这是 Microsoft.AspNetCore.SignalR.Client 3.1.3 的问题。
您可以在评论中阅读 here。
您可以等待更新或暂时修复此问题:
- 禁用协商
- 显式设置 WebSocket 传输
- 修改查询url
- 添加 OnMessageReceived 处理程序
客户端:
var token = await GetAccessToken();
var hubConnection = new HubConnectionBuilder()
.WithUrl($"/notification?access_token={token}", options =>
{
options.SkipNegotiation = true;
options.Transports = HttpTransportType.WebSockets;
options.AccessTokenProvider = GetAccessToken;
})
.Build();
服务器端:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options =>
{
// ...
})
.AddJwtBearer(options =>
{
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
(path.StartsWithSegments("/notification", System.StringComparison.InvariantCulture)))
{
context.Token = accessToken;
}
return Task.CompletedTask;
},
};
});
}
我在使用 CORS 和之后的 Websocket 时遇到了同样的错误。
在我的例子中,后备 longPolling 被用作连接有效但控制台记录错误 HTTP Authentication failed; no valid credentials available
.
的原因
如果您使用 Identity Server JWT,则以下代码解决了我的案例的错误。
(代码来自Microsoft SignalR Documentation - Authentication and authorization in ASP.NET Core SignalR - Identity Server JWT authentication)
services.AddAuthentication()
.AddIdentityServerJwt();
// insert:
services.TryAddEnumerable(
ServiceDescriptor.Singleton<IPostConfigureOptions<JwtBearerOptions>,
ConfigureJwtBearerOptions>());
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.Options;
public class ConfigureJwtBearerOptions : IPostConfigureOptions<JwtBearerOptions>
{
public void PostConfigure(string name, JwtBearerOptions options)
{
var originalOnMessageReceived = options.Events.OnMessageReceived;
options.Events.OnMessageReceived = async context =>
{
await originalOnMessageReceived(context);
if (string.IsNullOrEmpty(context.Token))
{
var accessToken = context.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
path.StartsWithSegments("/hubs"))
{
context.Token = accessToken;
}
}
};
}
}
重要提示:您的路线必须以 hubs
开头才能触发选项!
(参见第 path.StartsWithSegments("/hubs"))
行)
app.UseEndpoints(e =>
{
...
e.MapHub<ChatHub>("hubs/chat");
});
我正在尝试从我的 blazor webassembly 客户端连接到 SignalR 服务,但我认为这在 CORS 上失败了。这是我的剃刀文件中的代码。
m_connection = new HubConnectionBuilder()
.WithUrl(myMircoServiceUrl, options =>
{
options.AccessTokenProvider = () => Task.FromResult(userService.Token);
})
.WithAutomaticReconnect()
.Build();
await m_connection.StartAsync();
然后在 webassembly 日志记录中我看到以下错误:
从源“http://localhost:5010”获取 'xxxx/negotiate?negotiateVersion=1' 的访问已被 CORS 策略阻止:对预检请求的响应未通过访问控制检查:否 'Access-Control-Allow-Origin' header 出现在请求的资源上。如果不透明响应满足您的需求,请将请求的模式设置为 'no-cors' 以在禁用 CORS 的情况下获取资源。
我在 Blazor 服务器配置中添加了以下 CORS 策略,并在微服务配置中添加了类似内容:
app.UseResponseCompression();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBlazorDebugging();
}
else
{
app.UseExceptionHandler(@"/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseAuthorization();
app.UseCors(policy => policy
.WithOrigins("http://localhost:5010")
.AllowAnyHeader()
.AllowAnyMethod());
app.UseClientSideBlazorFiles<Client.Program>();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapFallbackToClientSideBlazor<Client.Program>(@"index.html");
});
有人知道哪里出了问题吗?
更新 1
我现在在 Chrome 控制台中看到以下错误:
dotnet.js:1 到 'ws://localhost:5000/hubs/posts?id=9Jxs0DhP924zgw_eIeE9Lg' 的 WebSocket 连接失败:HTTP 身份验证失败;没有可用的有效凭据
更新 2
我从 SignalR 集线器中删除了 [Authorize]
属性,现在它可以连接了。我可以向集线器发送消息。问题是这个属性是有原因的,因为我不希望人们可以订阅不适合他们的消息
更新 3
仍然没有进展。考虑使用 IdentityServer4 将身份验证提取到单独的微服务。最后状态是我有以下启动例程:
- 微服务:gist.github.com/njannink/15595b77ffe1c0593be1a555fa37f83f
- Blazor 服务器:gist.github.com/njannink/7302a888110e24d199ea45b66da4f26b
- Blazor 客户端:gist.github.com/njannink/add2568cbf48c8b3c070ccd4f28fd127
就我而言,ASP.NET Core 2.2 我有一个 API,我希望能够使用 API 中的 SignalR 连接到我的客户端应用程序。
我有
的项目- 网络API
- IdentityServer4
- MVC 客户端
以ASP.NET核心身份作为用户管理
为了让您的用户通过身份验证,您需要像这样实现一个 IUserIdProvider
public class IdBasedUserIdProvider : IUserIdProvider
{
public string GetUserId(HubConnectionContext connection)
{
//TODO: Implement USERID Mapper Here
//throw new NotImplementedException();
//return whatever you want to map/identify the user by here. Either ID/Email
return connection.User.FindFirst("sub").Value;
}
}
有了这个,我确保我正在将 ID/Email 推送到我从服务器或客户端调用的方法。虽然我总是可以在 HubContext 上使用 .User 并且它工作正常。
在我的网站 API Startup.cs 文件中我想出了
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(cfg =>
{
cfg.AddDefaultPolicy(policy =>
{
policy.WithOrigins(Configuration.GetSection("AuthServer:DomainBaseUrl").Get<string[]>())
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()
.SetIsOriginAllowed((_) => true)
.SetIsOriginAllowedToAllowWildcardSubdomains();
});
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, UserManager<AppUser> userManager,
RoleManager<IdentityRole> roleManager){
app.UseCors();
}
注意 Configuration.GetSection("AuthServer:DomainBaseUrl").Get() 从配置文件中检索允许 CORS 的域列表。
我在 My Client App COnfigureService Method 中做了这个配置
services.AddCors(cfg =>
{
cfg.AddDefaultPolicy(policy => {
policy.AllowAnyHeader();
policy.AllowAnyMethod();
policy.SetIsOriginAllowed((host) => true);
policy.AllowAnyOrigin();
});
});
希望对您的情况有所帮助。
最好的解决方案确实如 Ismail Umer 所描述的那样,使用类似 IdentityServer4 的独立身份验证服务。并在所有其他服务中使用此服务。这是我将在下一次迭代中做的事情。
作为短期解决方案,我临时将 blazor 服务器部分移到了我的 api 服务中,并使用双重身份验证方法(JWT header 或 cookie)。
var key = Encoding.UTF8.GetBytes(m_configuration[@"SecurityKey"]);
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = @"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier",
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true
};
})
.AddCookie();
// TODO: For time being support dual authorization. At later stage split in various micro-services and use IdentityServer4 for Auth
services.AddAuthorization(options =>
{
var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(
CookieAuthenticationDefaults.AuthenticationScheme,
JwtBearerDefaults.AuthenticationScheme);
defaultAuthorizationPolicyBuilder =
defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
options.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();
});
这是 Microsoft.AspNetCore.SignalR.Client 3.1.3 的问题。 您可以在评论中阅读 here。
您可以等待更新或暂时修复此问题:
- 禁用协商
- 显式设置 WebSocket 传输
- 修改查询url
- 添加 OnMessageReceived 处理程序
客户端:
var token = await GetAccessToken();
var hubConnection = new HubConnectionBuilder()
.WithUrl($"/notification?access_token={token}", options =>
{
options.SkipNegotiation = true;
options.Transports = HttpTransportType.WebSockets;
options.AccessTokenProvider = GetAccessToken;
})
.Build();
服务器端:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options =>
{
// ...
})
.AddJwtBearer(options =>
{
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
(path.StartsWithSegments("/notification", System.StringComparison.InvariantCulture)))
{
context.Token = accessToken;
}
return Task.CompletedTask;
},
};
});
}
我在使用 CORS 和之后的 Websocket 时遇到了同样的错误。
在我的例子中,后备 longPolling 被用作连接有效但控制台记录错误 HTTP Authentication failed; no valid credentials available
.
的原因
如果您使用 Identity Server JWT,则以下代码解决了我的案例的错误。
(代码来自Microsoft SignalR Documentation - Authentication and authorization in ASP.NET Core SignalR - Identity Server JWT authentication)
services.AddAuthentication()
.AddIdentityServerJwt();
// insert:
services.TryAddEnumerable(
ServiceDescriptor.Singleton<IPostConfigureOptions<JwtBearerOptions>,
ConfigureJwtBearerOptions>());
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.Options;
public class ConfigureJwtBearerOptions : IPostConfigureOptions<JwtBearerOptions>
{
public void PostConfigure(string name, JwtBearerOptions options)
{
var originalOnMessageReceived = options.Events.OnMessageReceived;
options.Events.OnMessageReceived = async context =>
{
await originalOnMessageReceived(context);
if (string.IsNullOrEmpty(context.Token))
{
var accessToken = context.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
path.StartsWithSegments("/hubs"))
{
context.Token = accessToken;
}
}
};
}
}
重要提示:您的路线必须以 hubs
开头才能触发选项!
(参见第 path.StartsWithSegments("/hubs"))
行)
app.UseEndpoints(e =>
{
...
e.MapHub<ChatHub>("hubs/chat");
});