Asp.Net Core使用SignalR通信(Cors跨域与Jwt权限验证)

一、前端


    <script src="js/jquery-3.3.1.min.js"></script>
    <script src="lib/SingnlR/signalr.js"></script>
    <script type="text/javascript">
        var url = "https://localhost:44314/chat"; //本地站点可以直接写“/chat”
        var token = "token"; // JWT验证码。不带Bearer
        const connection = new signalR.HubConnectionBuilder().withUrl(url, {
            accessTokenFactory: () => token
        }).build();
        connection.on("ReceiveMessage", (message) => {
            console.log(message);//后台返回的数据,可以是json格式
        });

        //发送给所有人
        document.getElementById("sendAllButton").addEventListener("click", event => {
            const message = “发给后台的数据”;
            //发送消息
            connection.invoke("SendToAll", message).catch(err => console.error(err.toString()));
            event.preventDefault();
        });
        //发送给单个人
        document.getElementById("sendOneButton").addEventListener("click", event => {
            const user = “接收者唯一ID”;
            const message = "发送给后台的数据";
            //发送消息;参数(“后台的函数名”,“后台函数参数1”,“后台函数参数2”)
            connection.invoke("Send", user, message).catch(err => console.error(err.toString()));
            event.preventDefault();
        });
        connection.start().catch(err => console.error(err.toString()));

二、后台

    [Authorize] //jwt权限验证特性
    public class SignalrHub : Hub, ITransientDependency //ABP的框架依赖,不使用abp的可忽略
    {
        public IAbpSession AbpSession { private get; set; }
        public ILogger Logger { private get; set; }
        //系统中的UserId与SignalR生成的ConnectionId的对应关系
        private static ConcurrentDictionary<long, string> userIds = new ConcurrentDictionary<long, string>();
        public SignalrHub()
        {
            AbpSession = NullAbpSession.Instance;
            Logger = NullLogger.Instance;
        }

        #region 建立连接时触发 +  override async Task OnConnectedAsync()
        /// <summary>
        /// 建立连接时触发
        /// </summary>
        /// <returns></returns>
        public override async Task OnConnectedAsync()
        {
            var userInfo = AbpSession.GetUserInfo();
            if (userInfo == null || userInfo.ShopId == 0)
            {
                Logger.Error("链接SignalR未获取到用户信息");
                return;
            }
            userIds.AddOrUpdate(userInfo.UserId, Context.ConnectionId, (uid, _) => Context.ConnectionId);
            //await Clients.All.SendAsync("ReceiveMessage", new { user = Context.ConnectionId, message = "我进来了" });
        }
        #endregion

        #region 离开连接时触发 + override async Task OnDisconnectedAsync(Exception ex)
        /// <summary>
        /// 离开连接时触发
        /// </summary>
        /// <param name="ex"></param>
        /// <returns></returns>
        public override async Task OnDisconnectedAsync(Exception ex)
        {
            var userInfo = AbpSession.GetUserInfo();
            if (userInfo == null || userInfo.ShopId == 0)
            {
                Logger.Error("退出SignalR未获取到用户信息");
                return;
            }

            userIds.Remove(userInfo.UserId, out string connectionId);
            //await Clients.All.SendAsync("ReceiveMessage", new { user = Context.ConnectionId, message = "我离开了" });
        }
        #endregion

        #region 向所有人推送消息 +  Task SendToAll(string message)
        /// <summary>
        /// 向所有人推送消息
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        public Task SendToAll(string message)
        {
            return Clients.All.SendAsync("ReceiveMessage", new { user = Context.ConnectionId, message = message });
        }
        #endregion

        #region 向指定组推送消息 + Task SendToGroup(string groupName, string message)
        /// <summary>
        /// 向指定组推送消息
        /// </summary>
        /// <param name="groupName"></param>
        /// <param name="message"></param>
        /// <returns></returns>
        public Task SendToGroup(string groupName, string message)
        {
            return Clients.Group(groupName).SendAsync("ReceiveMessage", new { user = Context.ConnectionId, message = message });
        }
        #endregion

        #region 加入指定组 + async Task JoinToGroup(string groupName)
        /// <summary>
        /// 加入指定组
        /// </summary>
        /// <param name="groupName"></param>
        /// <returns></returns>
        public async Task JoinToGroup(string groupName)
        {
            await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
        }
        #endregion

        #region 推出指定组 + async Task LeaveGroup(string groupName)
        /// <summary>
        /// 推出指定组
        /// </summary>
        /// <param name="groupName"></param>
        /// <returns></returns>
        public async Task LeaveGroup(string groupName)
        {
            await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
        }
        #endregion

        #region 向指定Id推送消息 + async Task Echo(string userid, string message)
        /// <summary>
        /// 向指定Id推送消息
        /// </summary>
        /// <param name="userid">要推送消息的对象</param>
        /// <param name="message"></param>
        /// <returns></returns>
        public async Task Send(string userid, string message)
        {
            await Clients.Client(userid).SendAsync("ReceiveMessage", new { user = Context.ConnectionId, message = message });
        }
        #endregion

三、Cors跨域

与平常的跨域配置没啥区别

        //允许跨域(Startup.ConfigureServices中)
        public IServiceProvider ConfigureServices(IServiceCollection services)
        {
            services.AddSignalR();
            services.AddCors(options =>
            {
                options.AddPolicy("CorsName", builder =>
                {
                    builder.AllowAnyOrigin()
                    .AllowAnyMethod()
                    .AllowAnyHeader().AllowCredentials());
                });
            });

        }
        
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseCors("CorsName"); // 设置全局跨域
            app.UseSignalR(routes =>
            {
                //SignalrHub为后台代码中继承Hub的类,“/chat”为请求路由地址;
                routes.MapHub<SignalrHub>("/chat"); 
                //可以配置多个地址
                routes.MapHub<。。。省略号>("。。。"); 
            });
        }

四、Jwt权限验证

与平常的Jwt配置多了几行代码, 看代码中的“重点”位置

services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = "JwtBearer";
    options.DefaultChallengeScheme = "JwtBearer";
}).AddJwtBearer("JwtBearer", options =>
{
    options.Audience = "Audience";
    options.TokenValidationParameters = new TokenValidationParameters
    {
        // The signing key must match!
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("SecurityKey")),
        // Validate the JWT Issuer (iss) claim
        ValidateIssuer = true,
        ValidIssuer = "Issuer",
        // Validate the JWT Audience (aud) claim
        ValidateAudience = true,
        ValidAudience = "Audience",
        // Validate the token expiry
        ValidateLifetime = true,
        // If you want to allow a certain Account of clock drift, set that here
        ClockSkew = TimeSpan.Zero
     };
    options.Events = new JwtBearerEvents
     {
        OnMessageReceived = (context) =>{
        if (!context.HttpContext.Request.Path.HasValue)
        {
            return Task.CompletedTask;
        }
        //重点在于这里;判断是Signalr的路径
        var accessToken = context.HttpContext.Request.Query["access_token"];
        var path = context.HttpContext.Request.Path;
        if (!(string.IsNullOrWhiteSpace(accessToken)) && path.StartsWithSegments("/chat"))
        {
            context.Token = accessToken;
            return Task.CompletedTask;
        }
        return Task.CompletedTask;
        }
    };
});

五、Nginx配置参考

里面包含了Nginx的SignalR配置https://blog.csdn.net/qq_26900081/article/details/102912807

发布了65 篇原创文章 · 获赞 28 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_26900081/article/details/90296333