Angular SignalR 和 Windows 身份验证 (NTLM)

Angular SignalR and Windows Authentication (NTLM)

我的问题很简单。我在 .NET Core 中使用 Windows 身份验证。在应用程序的前端部分 (Angular) 我有一个 HTTP 拦截器,它设置 widhcredentails: 到所有 HTTP 请求。

这个拦截器不能与 WebSocket 一起工作,所以我需要一个解决方案来将拦截器中的相同代码添加到 SignalR 服务。

这是我的拦截器代码:

intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>>
{
    request = request.clone({ withCredentials: true });
    return next.handle(request);
}

这是我的 SignalR 服务:

  this.requestConnection = new HubConnectionBuilder().
        withUrl(`${environment.apiUrl}/request`).
        withAutomaticReconnect([0, 2000, 30000, 60000, null]).
        build();
    this.requestConnection.
        start().
        then(() => console.log('Connection started')).
        catch(err => console.log(`Error while starting connection: ${err}`));

请记住,拦截器已添加到 app.module 组件的提供程序数组中:

providers: [
    {
        provide: HTTP_INTERCEPTORS,
        useClass: WinAuthInterceptor,
        multi: true
    }

],

SignalR 服务被注入根。

@Injectable({
    providedIn: 'root'
})

我在 Microsoft 文档中找到 this

If Windows authentication is configured in your app, SignalR can use that identity to secure hubs. However, to send messages to individual users, you need to add a custom User ID provider. The Windows authentication system doesn't provide the "Name Identifier" claim. SignalR uses the claim to determine the user name.

Add a new class that implements IUserIdProvider and retrieve one of the claims from the user to use as the identifier. For example, to use the "Name" claim (which is the Windows username in the form [Domain][Username]), create the following class:

public class NameUserIdProvider : IUserIdProvider
{
    public string GetUserId(HubConnectionContext connection)
    {
        return connection.User?.Identity?.Name;
    }
}

Rather than ClaimTypes.Name, you can use any value from the User (such as the Windows SID identifier, and so on).

Note: The value you choose must be unique among all the users in your system. Otherwise, a message intended for one user could end up going to a different user.

Register this component in your Startup.ConfigureServices method:

public void ConfigureServices(IServiceCollection services)
{
    // ... other services ...

    services.AddSignalR();
    services.AddSingleton<IUserIdProvider, NameUserIdProvider>();
}

In the .NET Client, Windows Authentication must be enabled by setting the UseDefaultCredentials property:

var connection = new HubConnectionBuilder()
    .WithUrl("https://example.com/chathub", options =>
    {
        options.UseDefaultCredentials = true;
    })
    .Build();

Windows authentication is supported in Internet Explorer and Microsoft Edge, but not in all browsers. For example, in Chrome and Safari, attempting to use Windows authentication and WebSockets fails. When Windows authentication fails, the client attempts to fall back to other transports which might work.

如前所述,并非所有浏览器都支持它。 IMO 验证信号 R 连接的最佳方法是使用承载令牌。

将令牌添加到连接非常简单:

this.chatHubConnection = new signalR.HubConnectionBuilder()
  .withUrl(this.endpointsService.chatHubConnection(), {
    accessTokenFactory: () => this.getCurrentJwtToken(),
  })
  .configureLogging(signalR.LogLevel.Error)
  .withAutomaticReconnect([0, 2000, 5000, 5000, 5000, 5000, 10000])
  .build();

我终于明白问题出在哪里了。由于我使用的是 Windows 身份验证,因此无需添加任何类型的 JWT 授权。此外,我在上面发布的拦截器代码没有任何区别,哈哈。这意味着您不需要 withCredetials: true 在拦截器或 SignalR 中。

无论如何,(如果您使用的是 IIS,则需要按照此 documentation 配置它,在本地它会完美运行,但如果您 hosting/deploying 您的应用程序在 IIS 上,则需要事先执行此操作,否则您的上下文将为 NULL)您需要在 [ 中将 anonymousAuthentication 属性 设置为 falselaunchSettings.json(.NET Core 作为后端)

  "iisSettings": {
"windowsAuthentication": true,
"anonymousAuthentication": false,
"iisExpress": {
  "applicationUrl": "http://localhost:64152",
  "sslPort": 44385
}

长话短说,Angular 代码如下所示:

  this.requestConnection = new HubConnectionBuilder().
        withUrl(`${environment.apiUrl}/request`).
        withAutomaticReconnect([0, 2000, 30000, 60000, null]).
        build();
    this.requestConnection.
        start().
        then(() => console.log('Connection started')).
        catch(err => console.log(`Error while starting connection: ${err}`));

在 .NET Core 中,我刚刚重写了 OnConnectAsync() class 来获取连接并执行一些逻辑:

    public override Task OnConnectedAsync()
    {
        var user = Context.User.Identity.Name.ToUpper();
        //remove domain name
        user = user[user.IndexOf("\")..];
        //remove speacial chars, excluding .
        user = UserProvider.RemoveSpecialCharacters(user);
        var conId = Context.ConnectionId;

        if (UserProvider.UserConnections.Any(key => key.Key == user))
        {
            UserProvider.UserConnections[user] = conId;
        }
        else
        {
            UserProvider.UserConnections.Add(user, conId);
        }


        return base.OnConnectedAsync();
    }