我有一个内置在ASP.NET Core7中的API.我有一个读取消息并用消息响应的MediatR控制器.客户端正在从ReactJS网站连接.我计划在不同的域上部署多个客户端站点.

我正在try 找到最不容易被欺骗的方法来获取客户端连接的域.我能够知道哪些域被允许连接,所以如果有帮助,我可以使用API密钥.虽然安全性不是一个大问题,但它将有助于我的指标,以了解哪些域正在连接.

到目前为止,我已经考虑过使用API密钥、自定义头(Referrer头)或读取CORS头.

推荐答案

我为你创建了一个非常简单的项目.请先判断一下检测结果.

enter image description here

我建议您可以生成临时安全密钥,并在创建集线器连接时将其附加.在我的样例代码中为:112233.

client-create-connetion

var connection = new signalR.HubConnectionBuilder().withUrl("/mainHub?uid=jason&sid=test&api-key=112233").configureLogging(signalR.LogLevel.Trace).build();

然后你可以在OnConnectedAsync中验证它,当然你可以把临时密钥存储在redis缓存或数据库中,当它被使用时,你可以删除它.它可以更安全地保护您的连接.

    public override async Task OnConnectedAsync()
    {
        string? apiKey = Context?.GetHttpContext()?.Request.Query["api-key"].ToString();
        // retrieve apiKey from db/redis and verify it
        if (apiKey == "112233")
        {
            await base.OnConnectedAsync();
        }
        else
        {
            Context.Abort();
        }

        // Get HttpContext In asp.net core signalr
        //IHttpContextFeature? hcf = this.Context?.Features?[typeof(IHttpContextFeature)] as IHttpContextFeature;
        //HttpContext? hc = hcf?.HttpContext;
        // userid
        string? uid = Context?.GetHttpContext()?.Request.Query["uid"].ToString();
        //string? uid = hc?.Request?.Path.Value?.Split(new string[] { "=", "" }, StringSplitOptions.RemoveEmptyEntries)[1].ToString();
        // system id
        string? sid = Context.GetHttpContext()?.Request.Query["sid"].ToString();
        //string? sid = hc?.Request?.Path.Value?.Split(new string[] { "=", "" }, StringSplitOptions.RemoveEmptyEntries)[2].ToString();

        string? userid = uid;

        if (userid == null || userid.Equals(string.Empty))
        {
            Trace.TraceInformation("userid is required, can't connect signalr service");
            return;
        }
        Trace.TraceInformation(userid + "connected");
        // save connection
        List<string>? existUserConnectionIds;
        ConnectedUsers.TryGetValue(userid, out existUserConnectionIds);
        if (existUserConnectionIds == null)
        {
            existUserConnectionIds = new List<string>();
        }
        existUserConnectionIds.Add(Context!.ConnectionId);
        ConnectedUsers.TryAdd(userid, existUserConnectionIds);

        await base.OnConnectedAsync();
    }

您可以使用var domain = context?.Request.Host.Host;来检索HubLogFilter内部的域.以下是为您提供的样例代码.

using Microsoft.AspNetCore.SignalR;
using System.Text.Json;
using System.Text;

namespace AspNetCore_SignalR
{
    public class HubLogFilter : IHubFilter
    {
        private readonly ILogger<HubLogFilter> _logger;
        private readonly HashSet<string> allowedDomains;

        public HubLogFilter(ILogger<HubLogFilter> logger)
        {
            _logger = logger;
            allowedDomains = new HashSet<string>
            {
                "a.com",
                "b.com",
                "localhost"
            };
        }

        public async ValueTask<object?> InvokeMethodAsync(
            HubInvocationContext invocationContext,
            Func<HubInvocationContext, ValueTask<object?>> next
        )
        {
            var startTime = DateTimeOffset.Now;
            var context = invocationContext.Context.GetHttpContext();
            var remoteIp = GetRemoteIpAddress(context);
            var userId = invocationContext.Context.UserIdentifier ?? "NonUser";
            // get the domain
            var domain = context?.Request.Host.Host;

            // check the domain
            if (!IsDomainAllowed(domain))
            {
                _logger.LogWarning("Blocked WebSocket connection from {Domain}", domain);
                context.Abort(); // if not allowed, abort the connection
                return null;
            }



            try
            {
                var result = await next(invocationContext);

                result = "failed";
                // this means we can find the message sent from client
                if (invocationContext.HubMethodArguments.Count > 0)
                {
                    result = "succeed";
                }

                var elapsed = DateTimeOffset.Now - startTime;
                var arguments = invocationContext.HubMethodArguments;
                var contentLength = System.Text.Json.JsonSerializer.Serialize(arguments).Length;

                //var contentLength = CalculateContentLength(invocationContext.HubMethodArguments);

                _logger.LogInformation(
                    "WebSocket {RequestPath}/{HubMethodName} {RemoteIpAddress} {UserId} executed in {Elapsed:0.0000} ms with content length {ContentLength}",
                    context?.Request.Path,
                    invocationContext.HubMethodName,
                    remoteIp,
                    userId,
                    elapsed.TotalMilliseconds,
                    contentLength
                );

                return result;
            }
            catch (Exception ex)
            {
                _logger.LogError($"Exception calling '{invocationContext.HubMethodName}': {ex}");
                throw;
            }
        }

        private static string GetRemoteIpAddress(HttpContext? context)
        {
            var remoteIp = context?.Request.Headers?["X-Forwarded-For"];
            if (string.IsNullOrEmpty(remoteIp))
            {
                remoteIp = context?.Connection.RemoteIpAddress?.ToString();
            }

            return remoteIp ?? "Unknown";
        }

        private static int CalculateContentLength(object?[] arguments)
        {
            var serializedArgs = JsonSerializer.Serialize(arguments);
            return Encoding.UTF8.GetByteCount(serializedArgs);
        }
        private bool IsDomainAllowed(string domain)
        {
            return allowedDomains.Contains(domain);
        }
    }
}

My Program.cs

        builder.Services.AddSignalR(hubOptions => {
            hubOptions.AddFilter<HubLogFilter>();
        });

Csharp相关问答推荐

无法更改或使用C#(WinForms.NET)中的全局变量

如何打印已添加到List的Linq值,而不是C#中的:System.Collections.Generic.List ' 1[System.Int32]?

需要更改哪些内容才能修复被覆盖的财产中的无效警告CS 8765?

在WPF.NET 6中使用C++/WinRT组件(但实际上是任何WinRT组件)

图形API基于appid列表检索多个应用程序

如何在C#中使用正则表达式抓取用逗号分隔的两个单词?

WeakReference未被垃圾收集

MigraDoc文档

WinForms在Linux上的JetBrains Rider中的应用

取决于您的数据量的多个嵌套循环

NET8 Maui&;iOS:AppCenter崩溃错误

从另一个不同 struct 的数组创建Newtonsoft.Json.Linq.J数组

错误CS1061';AuthenticationBuilder';不包含AddOpenIdConnect的定义

这是否比决定是否使用ConfigureAWait(False)更好?

如何使用实体框架核心对字符串_agg使用强制转换为varchar(Max)

使用免费的DotNet库从Azure函数向Azure文件共享上的现有Excel文件追加行

如何保存具有多个重叠图片框的图片框?

HttpClient,上传文件时实现进度

将文本从剪贴板粘贴到RichTextBox时,新文本不会在RichTextBox ForeColor中着色

为什么INTEGER在通过反射调用时对空对象返回TRUE,而在INTEGER上调用时返回FALSE?