在开发过程中,经常会遇到需要将系统嵌入到客户系统中的场景。这意味着需要将JavaScript包集成到他们的页面中,并与基于用户的系统进行通信。为了实现这一点,可能会考虑将应用程序拆分为两个独立的应用:主应用和集成部分。然而,这种方法在维护性、可测试性、可扩展性和环境设置方面存在诸多问题。为了解决这些问题,探索了一种新的方法,即使用SignalR和身份验证来实现主应用和客户端集成侧的相同控制和行为。
目标是创建一个React控件,它可以通过SignalR从不同的域与用户应用程序进行通信。为了实现这一点,需要配置控件,以便它可以与系统建立联系,同时让知道它来自哪个环境,以便可以决定身份验证的方式。
在实现过程中,首先考虑了使用IdentityServer 4,但后来发现客户要求从他们的系统中进行身份验证,而他们使用的是WCF,这使得IdentityServer 4不再适用。接下来,尝试了使用身份验证处理程序,但这是一个深不可测的兔子洞,没有带来太多价值。在时间压力下,最终选择了深入研究AuthenticationMiddleware。
AuthenticationMiddleware的代码如下所示(请更多地关注行为而不是代码本身,因为反编译的代码并不总是看起来像最初编写的那样):
public class AuthenticationMiddleware
{
private readonly RequestDelegate _next;
public AuthenticationMiddleware(RequestDelegate next, IAuthenticationSchemeProvider schemes)
{
_next = next ?? throw new ArgumentNullException(nameof(next));
Schemes = schemes ?? throw new ArgumentNullException(nameof(schemes));
}
public IAuthenticationSchemeProvider Schemes { get; set; }
public async Task Invoke(HttpContext context)
{
context.Features.Set(new AuthenticationFeature
{
OriginalPath = context.Request.Path,
OriginalPathBase = context.Request.PathBase
});
IAuthenticationHandlerProvider handlers = context.RequestServices.GetRequiredService
在上述代码中,最重要的一行用于身份验证是context.User = authenticateResult.Principal;,因为这实际上设置了在整个请求过程中要使用的user。
遗憾的是,Invoke方法不是虚拟的,唯一的选择是将其原样粘贴到代码库中,重命名以避免混淆,然后对其进行调整以满足需求。修改后的代码如下所示:
public class CustomAuthenticationMiddleware
{
private readonly RequestDelegate _next;
private readonly IConfiguration _configuration;
private readonly IServiceProvider _serviceProvider;
public CustomAuthenticationMiddleware(RequestDelegate next, IAuthenticationSchemeProvider schemes, IConfiguration configuration, IServiceProvider serviceProvider)
{
_next = next ?? throw new ArgumentNullException(nameof(next));
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
Schemes = schemes ?? throw new ArgumentNullException(nameof(schemes));
_serviceProvider = serviceProvider;
}
public IAuthenticationSchemeProvider Schemes { get; set; }
public async Task Invoke(HttpContext context)
{
context.Features.Set(new AuthenticationFeature
{
OriginalPath = context.Request.Path,
OriginalPathBase = context.Request.PathBase
});
if (context.Request.Path.Value.Contains("someArbitraryPath"))
{
IServiceProvider scopedServiceProvider = _serviceProvider.CreateScope().ServiceProvider;
IUserClaimsPrincipalFactory claimsPrincipalFactory = scopedServiceProvider.GetRequiredService
在这个示例中,注入了IConfiguration和IServiceProvider,因为场景需要为不同的环境配置,并且希望使用服务提供商中的存储库和自定义服务。
需要注意的一点是,代码中有一行_serviceProvider.CreateScope().ServiceProvider;,因为发现有些服务不能从根ServiceProvider创建,所以为了使其工作,需要创建一个特定的作用域来检索它们。
在实现自定义身份验证中间件时,需要考虑以下几点: 可以引入任何想要的自定义逻辑,使其可配置,并根据传入的请求做出决策,包括cookies、headers以及可能到达的任何其他内容。 这不会从HttpContext的角度影响应用程序的其他部分,或者任何控制器,用户就像任何其他用户一样有效且经过身份验证。 由于这不像正常的身份验证那样基于cookie,需要确保正在执行的检查非常快速且非常特定,就像在例子中,一旦SignalR协商完成,如果最终使用WebSockets,它就不太可能再次运行(假设,可能是错的)。