英文:
Adding Authorization to one of my SignalR Hubs leads to WebSocket failing to connect
问题
我正在使用SignalR Hub来处理在线国际象棋应用程序的匹配,我正在尝试使用JWT授权限制Hub的使用。
这是Hub背后的代码:
```csharp
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class GameHub : Hub
{
    private readonly ApplicationDbContext context;
    public static List<Player> _Players = new List<Player>();
    public GameHub(ApplicationDbContext context)
    {
        this.context = context;
    }
    public async Task SendMessage(string user, string message)
    {   
        await Clients.All.SendAsync("ReceiveMessage", user, message);
    }
    public async Task JoinQueue(string userEmail)
    {
        Player player = context.Players.Where(x => x.Email == userEmail).FirstOrDefault();
        Player player2 = null;  
        if (player != null)
        {
            _Players.Add(player);
        }
        else
            return;
        await Clients.All.SendAsync("QueuePlayerCountUpdate", _Players.Count);
        
        bool foundMatch = false;
        int eloRange = 50;
        while (!foundMatch) {
            if (_Players.Where(x => player.Elo - eloRange <= x.Elo && 
            x.Elo <= player.Elo + eloRange && 
            x.PlayerId != player.PlayerId).Any())
                player2 = _Players.Where(x => player.Elo - eloRange <= x.Elo 
&& x.Elo <=                  player.Elo + eloRange).FirstOrDefault();
            if(player2 == null)
            {
                Thread.Sleep(5000);
                eloRange += 50;
            }
            else
            {
                _Players.Remove(player);
                _Players.Remove(player2);
                foundMatch = true;
                Clients.Caller.SendAsync("MatchFound",player2.Email);
                Clients.User(player2.AspNetUserId).SendAsync("MatchFound", player.Email);
            } 
        }    
    }
}
这是Hub的后台代码。
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using System.Text;
namespace BackEnd_ASP.NET
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }
        public IConfiguration Configuration { get; }
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
            services.AddControllers();
            services.AddEndpointsApiExplorer();
            services.AddSwaggerGen();
            services.AddSignalR();
            services.AddCors(options =>
            {
                var frontendURL = Configuration.GetValue<string>("frontend_url");
                options.AddDefaultPolicy(builder =>
                {
                    builder.WithOrigins("http://localhost:3000").AllowAnyMethod().AllowAnyHeader().AllowCredentials();
                });
            });
            services.AddIdentity<IdentityUser, IdentityRole>().
                AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders();
            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(
                options =>
                {
                    options.Events = new JwtBearerEvents
                    {
                        OnMessageReceived = context =>
                        {
                            var accessToken = context.Request.Query["access_token"];
                            if (!string.IsNullOrEmpty(accessToken))
                            {
                                context.Token = accessToken;
                            }
                            return Task.CompletedTask;
                        }
                    };
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateIssuer = false,
                        ValidateAudience = false,
                        ValidateLifetime = false,
                        ValidateIssuerSigningKey = true,
                        IssuerSigningKey = new SymmetricSecurityKey
                        (Encoding.UTF8.GetBytes(Configuration["keyjwt"])),
                        ClockSkew = TimeSpan.Zero
                    };
                    
                });
        }
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseSwagger();
                app.UseSwaggerUI();
            }
            app.UseRouting();
            app.UseCors();  
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
                endpoints.MapHub<GameHub>("/GameHub");
            });
            app.UseAuthorization();
        }
    }
}
这是启动配置,以及连接初始化如下:
const connection = new HubConnectionBuilder().
            withUrl("https://localhost:7244/GameHub",{
                accessTokenFactory: ()=> {return `bearer ${token}`},  
                skipNegotiation: true,
                transport: HttpTransportType.WebSockets
              }).
            configureLogging(LogLevel.Information).build(); 
如果简单地删除Authorization标签,一切都按预期工作。我尝试将授权标签移到端点上,或者同时放在端点和整个Hub上,我还尝试删除令牌中的bearer,尝试删除skip negotiation选项,并尝试以几种方式修改OnMessageReceived,但都没有成功。我确保在IIS Windows服务器中启用了套接字。
<details>
<summary>英文:</summary>
I am using a SignlaR Hub to handle matchmaking for an online chess application, and I'm trying to restrict usage of the hub with JWT authorization.
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class GameHub : Hub
{
private readonly ApplicationDbContext context;
public static List<Player> _Players = new List<Player>();
    public GameHub(ApplicationDbContext context)
{
this.context = context;
}
public async Task SendMessage(string user, string message)
{   
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
public async Task JoinQueue(string userEmail)
{
Player player = context.Players.Where(x => x.Email == userEmail).FirstOrDefault();
Player player2 = null;  
if (player != null)
{
_Players.Add(player);
}
else
return;
await Clients.All.SendAsync("QueuePlayerCountUpdate", _Players.Count);
bool foundMatch = false;
int eloRange = 50;
while (!foundMatch) {
if (_Players.Where(x => player.Elo - eloRange <= x.Elo && 
x.Elo <= player.Elo + eloRange && 
x.PlayerId != player.PlayerId).Any())
player2 = _Players.Where(x => player.Elo - eloRange <= x.Elo 
&& x.Elo <=                  player.Elo + eloRange).FirstOrDefault();
if(player2 == null)
{
Thread.Sleep(5000);
eloRange += 50;
}
else
{
_Players.Remove(player);
_Players.Remove(player2);
foundMatch = true;
Clients.Caller.SendAsync("MatchFound",player2.Email);
Clients.User(player2.AspNetUserId).SendAsync("MatchFound", player.Email);
}
        }
}
}
 this is the code behind the hub,
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using System.Text;
namespace BackEnd_ASP.NET
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
    public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddControllers();
services.AddEndpointsApiExplorer();
services.AddSwaggerGen();
services.AddSignalR();
services.AddCors(options =>
{
var frontendURL = Configuration.GetValue<string>("frontend_url");
options.AddDefaultPolicy(builder =>
{
builder.WithOrigins("http://localhost:3000").AllowAnyMethod().AllowAnyHeader().AllowCredentials();
});
});
services.AddIdentity<IdentityUser, IdentityRole>().
AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(
options =>
{
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
if (!string.IsNullOrEmpty(accessToken))
{
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey
(Encoding.UTF8.GetBytes(Configuration["keyjwt"])),
ClockSkew = TimeSpan.Zero
};
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseRouting();
app.UseCors();  
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<GameHub>("/GameHub");
});
app.UseAuthorization();
}
}
}
this is the startup 
and this is the connection initialization 
const connection = new HubConnectionBuilder().
withUrl("https://localhost:7244/GameHub",{
accessTokenFactory: ()=> {return bearer ${token}},
skipNegotiation: true,
transport: HttpTransportType.WebSockets
}).
configureLogging(LogLevel.Information).build();
If i simply remove the Authorization tag everything works as expected. I have tried moving the authorization tag to the endpoint, or having it both on the endpoint and the entire hub, i have also tried removing bearer from the token,i have tried removing the skip negotiation option,and tried modifying the   
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
                        if (!string.IsNullOrEmpty(accessToken))
{
context.Token = accessToken;
}
return Task.CompletedTask;
} 
in a couple of ways, none of which have worked. I have made sure to have sockets enabled in IIS Windows server.
</details>
# 答案1
**得分**: 1
请将您的代码更改如下,问题将得到解决。请查看官方文档:<br/>
[ASP.NET Core SignalR 中的身份验证和授权][1]<br/>
[中间件顺序][2]
```csharp
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseRouting();
app.UseCors();  
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<GameHub>("/GameHub");
});
//app.UseAuthorization();
}
英文:
Change your code like below and the issue will be fixed. Please check the official doc : <br/>
Authentication and authorization in ASP.NET Core SignalR
<br/>
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseRouting();
app.UseCors();  
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<GameHub>("/GameHub");
});
//app.UseAuthorization();
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论