ASP.Net Core 身份验证 SignalR 和 Cors
在我当前的设置中,我使用的是在与 ASP.net Core 服务器不同的计算机上运行的 SPA (ReactJS)。除了身份验证之外,整个客户端-服务器通信都通过 SignalR 运行。为了安全起见,我想使用基于 cookie 的身份验证。作为用户管理,我使用 ASP.net.Core.Identity。 设置 CORS 有效,现在我正在实现基于简单 HTTP 请求的登录/注销机制。
工作流程如下:
- 通过简单的 HTTP 向服务器发送登录请求。
(有效)
- 成功登录后在客户端设置 cookie。
(有效)
- 客户端启动 SignalR 集线器连接(使用提供的 cookie)。
(Works)
- 客户端通过 HubConnetion 向服务器发送数据。
(Works)
- 服务器根据
Claimspricipal
发送数据。(失败)
身份验证成功后,客户端收到 cookie 并在与 signalR-hub 协商时使用它。当调用其他控制器端点时,我可以访问之前经过身份验证的 ClaimsPrincipal。
现在我的问题是:访问 HubCallerContext 时,未设置 ClaimsPrincipal (设置了身份或声明)。我是否还需要在此上下文中注册 ClaimsPrincipal 还是将由 ASP.net 处理?有关身份验证的其他示例假设调用方和集线器在同一服务器上运行。我是否错过或误解了什么?
这里是我的 Startup.cs
:
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<SignInManager<ApplicationUser>, ApplicationSignInManager>();
services.AddScoped<IAuthorizationHandler, MyRequirementHandler>();
services.AddCors(
options => options.AddPolicy("CorsPolicy",
builder =>
{
builder
.SetIsOriginAllowed(origin =>
{
if (string.IsNullOrEmpty(origin)) return false;
if (origin.ToLower().StartsWith("http://localhost")) return true;
return false;
})
.AllowAnyHeader()
.AllowCredentials();
})
);
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme);
services.AddAuthorization(options =>
{
options.AddPolicy("PolicyName",
policy => { policy.Requirements.Add(new MyRequirement()); });
});
services.ConfigureApplicationCookie(config =>
{
config.Cookie.HttpOnly = false;
config.Cookie.Name = "my-fav-cookie";
config.ExpireTimeSpan = TimeSpan.FromDays(14);
config.Cookie.SameSite = SameSiteMode.None;
config.Cookie.SecurePolicy = CookieSecurePolicy.Always;
config.SlidingExpiration = false;
});
services.AddSignalR(opt => { opt.EnableDetailedErrors = true; })
.AddMessagePackProtocol(option => { option.SerializerOptions.WithResolver(StandardResolver.Instance); })
.AddNewtonsoftJsonProtocol(option =>
{
option.PayloadSerializerSettings.DateFormatHandling = DateFormatHandling.IsoDateFormat;
option.PayloadSerializerSettings.DateParseHandling = DateParseHandling.DateTime;
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerManager logger,
ILoggerFactory loggerFactory)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors("CorsPolicy");
app.UseRouting();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
...some Hubendpoints...
});
}
这里是 MyRequirement.cs
:
public class MyRequirement : IAuthorizationRequirement
{
}
这里是 MyRequirementHandler.cs
:
public class
MyRequirementHandler: AuthorizationHandler<MyRequirement,
HubInvocationContext>
{
private readonly IAuthorizationHelper _authorizationHelper;
private readonly UserManager<ApplicationUser> _userManager;
public MyRequirementHandler(
IAuthorizationHelper authorizationHelper,
UserManager<ApplicationUser> userManager)
{
_authorizationHelper = authorizationHelper;
_userManager = userManager;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context,
MyRequirement requirement,
HubInvocationContext resource)
{
var user = context.User;
...
}
}
还有 Hub.cs:
public class Hub: Hub<ISystemClient>
{
private readonly ISystemService _systemService;
public Hub(ISystemService systemService)
{
_systemService = systemService;
}
[Authorize("PolicyName")]
public async Task DoSthFancy()
{
var user = Context.User;
var fancyStruff = await _systemService.GetFancyStuff(user);
await Clients.Caller.SendFancyStuff(fancyStuff);
}
}
感谢您的帮助!
编辑(2022 年 11 月 4 日) 由于复制粘贴错误,我编辑/修复了 Startup.cs 的代码片段。谢谢你的提示。
In my current setup I am using a SPA (ReactJS) running on a different machine than my ASP.net Core Server. The whole client-server communication runs through SignalR except authentication. For security I want to use cookie-based authentication. As user-management I am using ASP.net.Core.Identity.
Setting up CORS works and now I'm implementing a login/logout mechanism based on simple HTTP-Request.
Here the workflow:
- Send SignIn-Request to server via simple HTTP.
(Works)
- After successfully signin set cookie on client.
(Works)
- Client starts SignalR hub-connections (using the provided cookie).
(Works)
- Client sends data to server via HubConnetion.
(Works)
- Server sends data based on
Claimspricipal
.(Fails)
After the successfull authentication the client receives the cookie and uses it when negotiating with the signalR-hubs. When calling other controller-endpoints I have access to the ClaimsPrincipal which authenticated previously.
Now to my problem: when accessing the HubCallerContext the ClaimsPrincipal is not set (identity nor claims are set). Do I need to register the ClaimsPrincipal on this context as well or will it be handled by ASP.net? Other examples about authentication are assumuming that caller and hub are running on the same server. Did I missed or missunderstood something?
Here my Startup.cs
:
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<SignInManager<ApplicationUser>, ApplicationSignInManager>();
services.AddScoped<IAuthorizationHandler, MyRequirementHandler>();
services.AddCors(
options => options.AddPolicy("CorsPolicy",
builder =>
{
builder
.SetIsOriginAllowed(origin =>
{
if (string.IsNullOrEmpty(origin)) return false;
if (origin.ToLower().StartsWith("http://localhost")) return true;
return false;
})
.AllowAnyHeader()
.AllowCredentials();
})
);
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme);
services.AddAuthorization(options =>
{
options.AddPolicy("PolicyName",
policy => { policy.Requirements.Add(new MyRequirement()); });
});
services.ConfigureApplicationCookie(config =>
{
config.Cookie.HttpOnly = false;
config.Cookie.Name = "my-fav-cookie";
config.ExpireTimeSpan = TimeSpan.FromDays(14);
config.Cookie.SameSite = SameSiteMode.None;
config.Cookie.SecurePolicy = CookieSecurePolicy.Always;
config.SlidingExpiration = false;
});
services.AddSignalR(opt => { opt.EnableDetailedErrors = true; })
.AddMessagePackProtocol(option => { option.SerializerOptions.WithResolver(StandardResolver.Instance); })
.AddNewtonsoftJsonProtocol(option =>
{
option.PayloadSerializerSettings.DateFormatHandling = DateFormatHandling.IsoDateFormat;
option.PayloadSerializerSettings.DateParseHandling = DateParseHandling.DateTime;
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerManager logger,
ILoggerFactory loggerFactory)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors("CorsPolicy");
app.UseRouting();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
...some Hubendpoints...
});
}
Here the MyRequirement.cs
:
public class MyRequirement : IAuthorizationRequirement
{
}
And here the MyRequirementHandler.cs
:
public class
MyRequirementHandler: AuthorizationHandler<MyRequirement,
HubInvocationContext>
{
private readonly IAuthorizationHelper _authorizationHelper;
private readonly UserManager<ApplicationUser> _userManager;
public MyRequirementHandler(
IAuthorizationHelper authorizationHelper,
UserManager<ApplicationUser> userManager)
{
_authorizationHelper = authorizationHelper;
_userManager = userManager;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context,
MyRequirement requirement,
HubInvocationContext resource)
{
var user = context.User;
...
}
}
And the Hub.cs
:
public class Hub: Hub<ISystemClient>
{
private readonly ISystemService _systemService;
public Hub(ISystemService systemService)
{
_systemService = systemService;
}
[Authorize("PolicyName")]
public async Task DoSthFancy()
{
var user = Context.User;
var fancyStruff = await _systemService.GetFancyStuff(user);
await Clients.Caller.SendFancyStuff(fancyStuff);
}
}
Thanks for your help!
EDIT (04-11-2022)
I edited/fixed the code-snippet of the Startup.cs
due to bad copy-paste. Thanks for the hint.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
在逐步实施示例之后,我发现ASP仅在您通过添加
[授权]
属性来挑选HubContext内部的索赔。因此,缺少的是实现AuthenticationHandler,这是配置中的注册并设置了授权 - 属性。在startup.cs中,您需要注册身份验证:
在枢纽中:
希望它能帮助其他人!
After implementing an example step by step I figured out that ASP sets the ClaimsPrincipal inside the HubContext only when you are challeging the hub-endpoint or the hub itself by adding a
[Authorization]
attribute. So what was missing was a implementation of an AuthenticationHandler, a registration in the configuration and setting the authorization-attribute.In the Startup.cs you need to register the Authentication:
and in the Hub:
Hope it will help others!