ASP.Net 核心认证 SignalR 和 Cors
ASP.Net Core Authentication SignalR and Cors
在我当前的设置中,我在与 ASP.net 核心服务器不同的机器上使用 SPA (ReactJS) 运行。除身份验证外,整个客户端-服务器通信都通过 SignalR 运行。为了安全起见,我想使用基于 cookie 的身份验证。作为用户管理,我正在使用 ASP.net.Core.Identity。
设置 CORS 有效,现在我正在实施基于简单 HTTP 请求的 login/logout 机制。
这里是工作流程:
- 通过简单的 HTTP 向服务器发送登录请求。
(Works)
- 登录成功后在客户端设置cookie。
(Works)
- 客户端启动 SignalR 集线器连接(使用提供的 cookie)。
(Works)
- 客户端通过HubConnetion向服务器发送数据。
(Works)
- 服务器根据
Claimspricipal
发送数据。 (Fails)
成功验证后,客户端会收到 cookie 并在与 signalR-hubs 协商时使用它。调用其他控制器端点时,我可以访问之前经过身份验证的 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);
}
}
感谢您的帮助!
编辑 (04-11-2022)
由于错误的复制粘贴,我 edited/fixed Startup.cs
的代码片段。谢谢指点。
在逐步实施示例后,我发现 ASP 仅当您通过添加 [Authorization]
挑战 hub-endpoint 或集线器本身时才在 HubContext 中设置 ClaimsPrincipal属性。所以缺少的是 AuthenticationHandler 的实现,配置中的注册和设置 authorization-attribute.
public class CustomAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
public CustomAuthenticationHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock,
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager) : base(options, logger, encoder, clock)
{
_userManager = userManager;
_signInManager = signInManager;
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (Context.Request.Cookies.TryGetValue("my-fav-cookie", out var cookie))
{
var user = Context.Request.HttpContext.User;
var ticket = new AuthenticationTicket(user, new(), CustomCookieScheme);
return Task.FromResult(AuthenticateResult.Success(ticket));
}
return Task.FromResult(AuthenticateResult.Fail("my-fav-cookie"));
}
}
在Startup.cs中您需要注册身份验证:
services.AddAuthentication().AddScheme<AuthenticationSchemeOptions, CustomAuthenticationHandler>(
"MyCustomScheme", _ => { });
在中心:
namespace My.HubProject {
[Authorize(AuthenticationScheme="MyCustomScheme")]
public class MyHub : Hub {
...
}
}
希望对大家有所帮助!
在我当前的设置中,我在与 ASP.net 核心服务器不同的机器上使用 SPA (ReactJS) 运行。除身份验证外,整个客户端-服务器通信都通过 SignalR 运行。为了安全起见,我想使用基于 cookie 的身份验证。作为用户管理,我正在使用 ASP.net.Core.Identity。 设置 CORS 有效,现在我正在实施基于简单 HTTP 请求的 login/logout 机制。
这里是工作流程:
- 通过简单的 HTTP 向服务器发送登录请求。
(Works)
- 登录成功后在客户端设置cookie。
(Works)
- 客户端启动 SignalR 集线器连接(使用提供的 cookie)。
(Works)
- 客户端通过HubConnetion向服务器发送数据。
(Works)
- 服务器根据
Claimspricipal
发送数据。(Fails)
成功验证后,客户端会收到 cookie 并在与 signalR-hubs 协商时使用它。调用其他控制器端点时,我可以访问之前经过身份验证的 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);
}
}
感谢您的帮助!
编辑 (04-11-2022)
由于错误的复制粘贴,我 edited/fixed Startup.cs
的代码片段。谢谢指点。
在逐步实施示例后,我发现 ASP 仅当您通过添加 [Authorization]
挑战 hub-endpoint 或集线器本身时才在 HubContext 中设置 ClaimsPrincipal属性。所以缺少的是 AuthenticationHandler 的实现,配置中的注册和设置 authorization-attribute.
public class CustomAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
public CustomAuthenticationHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock,
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager) : base(options, logger, encoder, clock)
{
_userManager = userManager;
_signInManager = signInManager;
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (Context.Request.Cookies.TryGetValue("my-fav-cookie", out var cookie))
{
var user = Context.Request.HttpContext.User;
var ticket = new AuthenticationTicket(user, new(), CustomCookieScheme);
return Task.FromResult(AuthenticateResult.Success(ticket));
}
return Task.FromResult(AuthenticateResult.Fail("my-fav-cookie"));
}
}
在Startup.cs中您需要注册身份验证:
services.AddAuthentication().AddScheme<AuthenticationSchemeOptions, CustomAuthenticationHandler>(
"MyCustomScheme", _ => { });
在中心:
namespace My.HubProject {
[Authorize(AuthenticationScheme="MyCustomScheme")]
public class MyHub : Hub {
...
}
}
希望对大家有所帮助!