第八节:基于Core6.0中间件实现SignalR的安全校验 以及 部署IIS注意的问题

一.  中间件实现安全校验

(官方默认的方式详见:https://learn.microsoft.com/zh-cn/aspnet/core/signalr/authn-and-authz?view=aspnetcore-6.0   个人不喜欢)

1.  SignalR的注册

代码如下:

//1. 注册SignalR
services.AddSignalR();
//2. 允许跨域
app.UseCors(options =>
{
    string[] urls  = new string[] { "http://11.12.216.184:8001", "http://127.0.0.1:8848"};
    options.WithOrigins(urls)
          .AllowAnyMethod()
          .AllowAnyHeader()
          .AllowCredentials();  //signalR需要开启AllowCredentials
});
//3. 开启SignalR (这里/myCrewHub可以是任意值,对应着前端路径)
app.MapHub<CrewHub>("/myCrewHub");

剖析:

  (1).  SignalR要求必须开启 AllowCredentials,而AllowCredentials需要和WithOrigins配对出现。

  (2).  这里/myCrewHub可以是任意值,对应着前端路径,

var myConnection = new signalR.HubConnectionBuilder().withUrl(`http://xxx.xx.xx.xx:xx/myCrewHub`).build();

 

2.  中间件 及 注册

中间件代码:

查看代码
 /// <summary>
/// 校验SignalR的中间件
/// (效果:SignalR校验不通过,仅signalr无法正常使用,其它http请求不受影响)
/// </summary>
public class CheckHubMiddleware
{
    private readonly RequestDelegate _next;

    public CheckHubMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        var requestPath = context.Request.Path.Value;
        if (requestPath.Equals("/myCrewHub"))
        {
            var accessToken = context.Request.Query["access_token"].ToString();
            if (string.IsNullOrEmpty(accessToken))
            {
                context.Response.StatusCode = 401;
                return;
            }
            //校验token的正确性
            var result = JWTHelp.JWTJieM(accessToken, ConfigHelp.GetString("JWTSecret"));
            if (result == "expired")
            {
                context.Response.StatusCode = 401;
                return;
            }
            else if (result == "invalid")
            {
                context.Response.StatusCode = 401;
                return;
            }
            else if (result == "error")
            {
                context.Response.StatusCode = 401;
                return;
            }
            else
            {
                //表示校验通过,直接跳过,继续运行即可
                await _next.Invoke(context);
            }
        }
        else
        {
            //表示非SignalR连接,直接跳过,继续运行即可
            await _next.Invoke(context);
        }
    }
}

注册中间件

//Signalr校验中间件
app.UseMiddleware<CheckHubMiddleware>();

剖析:

  (1).  if (requestPath.Equals("/myCrewHub"))  表示该过滤器仅对SignalR请求起作用,此处需要注意,在项目的其它接口中,应该回避/myCrewHub这一路径。

  (2).  token值要从 Query["access_token"] 中获取,这是固定值,这对应这前端的赋值方式:通过accessTokenFactory方法进行赋值

var myConnection = new signalR.HubConnectionBuilder().withUrl(`${baseUrl}/myCrewHub`, {
								accessTokenFactory: () => myUtils.sessionGet('token'),
								skipNegotiation: true, //跳过协商,永远走websocket协议
								transport: signalR.HttpTransportType.WebSockets
							})
				  .withAutomaticReconnect([3000, 20000, 60000, 60000]) //依次再过3s、20s、60s、60s进行重连
				  .build();

3.  前端代码

代码分享:

                       //9.1 初始化连接
						var myConnection = new signalR.HubConnectionBuilder()
							.withUrl(`${baseUrl}/myCrewHub`, {
								accessTokenFactory: () => myUtils.sessionGet('token'),
								skipNegotiation: true, //跳过协商,永远走websocket协议
								transport: signalR.HttpTransportType.WebSockets
							})
							.withAutomaticReconnect([3000, 20000, 60000, 60000]) //依次再过3s、20s、60s、60s进行重连
							.build();

						//9.2 进行连接
						myConnection.start().then(() => {
							console.log('------signalR连接成功------');
							//调用方法,进行关联
							myConnection.invoke("SaveConnectionId", myUtils.sessionGet('token'));
						}).catch(err => console.error(err.toString()));

						//9.3 监听方法-进行证书表格的刷新
						myConnection.on("UpdateCertTables", () => {
							console.log('-------我是UpdateCertTables我收到更新指示------')
							pageUtils.initZsTable();
							// 初始化自动resize的标识字段
							pageUtils.initAutoResizeFlag();
						});

						// 9.4 掉线重连机制
						//(默认4次重连),任何一次只要回调成功,调用
						myConnection.onreconnected(connectionId => {
							console.log('-------触发重连机制------')
							//调用方法,进行关联
							myConnection.invoke("SaveConnectionId", myUtils.sessionGet('token'));
						});

剖析:

  (1).  通过accessTokenFactory: () => myUtils.sessionGet('token') 实现对 access_token 字段的赋值。

4. 测试

(1). 当token值校验通过的时候,如下:

(2).  当token校验不通过的时,如下:   (注:其它http请求不受影响)

 

 

 

二.  IIS部署问题

  IIS需要安装WebSocket模块。

 

 

 

 

 

 

 

 

 

!

  • 作       者 : Yaopengfei(姚鹏飞)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 声     明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
  • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 
posted @ 2022-12-14 16:03  Yaopengfei  阅读(426)  评论(1编辑  收藏  举报