[ASP.NET Core]WebSocket 사용해보기
29516 단어 charpaspnetcore
소개
이번에는 WebSocket 애플리케이션에 몇 가지 기능을 추가해 보겠습니다.
환경
입증
WebSocket에는 인증에 관한 사양이 없습니다.
많은 샘플이 쿠키, 세션 또는 URL 매개변수로 토큰을 추가하여 인증하는 것을 발견했습니다.
이번에는 세션에 JWT를 추가합니다.
Program.cs
using System.Net;
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using NLog.Web;
using WebRtcSample.Models;
using WebRtcSample.Users;
using WebRtcSample.Users.Repositories;
using WebRtcSample.WebSockets;
var logger = NLogBuilder.ConfigureNLog(Path.Combine(Directory.GetCurrentDirectory(), "Nlog.config"))
.GetCurrentClassLogger();
try
{
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseUrls("http://0.0.0.0:5027");
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
ClockSkew = TimeSpan.FromSeconds(30),
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])),
};
});
builder.Services.AddSession(options => {
options.IdleTimeout = TimeSpan.FromSeconds(30);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
options.Cookie.SameSite = SameSiteMode.Strict;
});
builder.Services.AddRazorPages();
builder.Services.AddControllers();
builder.Services.AddHttpContextAccessor();
builder.Services.AddDbContext<SampleContext>(options =>
{
options.EnableSensitiveDataLogging();
options.UseNpgsql(builder.Configuration["DbConnection"]);
});
builder.Services.AddIdentity<ApplicationUser, IdentityRole<int>>()
.AddUserStore<ApplicationUserStore>()
.AddEntityFrameworkStores<SampleContext>()
.AddDefaultTokenProviders();
builder.Services.AddSingleton<IWebSocketHolder, WebSocketHolder>();
builder.Services.AddScoped<IApplicationUserService, ApplicationUserService>();
builder.Services.AddScoped<IUserTokens, UserTokens>();
builder.Services.AddScoped<IApplicationUsers, ApplicationUsers>();
var app = builder.Build();
app.UseSession();
// this line must be executed before UseRouting().
app.Use(async (context, next) =>
{
var token = context.Session.GetString("user-token");
if(string.IsNullOrEmpty(token) == false)
{
context.Request.Headers.Add("Authorization", $"Bearer {token}");
}
await next();
});
app.UseStaticFiles();
app.UseWebSockets();
app.UseStatusCodePages(async context =>
{
if (context.HttpContext.Response.StatusCode == (int)HttpStatusCode.Unauthorized)
{
if(context.HttpContext.Request.Path.StartsWithSegments("/") ||
context.HttpContext.Request.Path.StartsWithSegments("/pages"))
{
context.HttpContext.Response.Redirect("/pages/signin");
return;
}
}
await context.Next(context.HttpContext);
});
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
// this line must be executed after setting tokens and authentications.
app.MapWebSocketHolder("/ws");
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.Run();
}
catch (Exception ex) {
logger.Error(ex, "Stopped program because of exception");
throw;
}
finally {
NLog.LogManager.Shutdown();
}
세션 값은 WebSocket에 연결한 후에도 유지됩니다.
WebSocketHolder.cs
using System.Collections.Concurrent;
using System.Net.WebSockets;
namespace WebRtcSample.WebSockets
{
public class WebSocketHolder: IWebSocketHolder
{
private readonly ILogger<WebSocketHolder> logger;
private readonly IHttpContextAccessor httpContext;
private readonly ConcurrentDictionary<string, WebSocket> clients = new ();
private CancellationTokenSource source = new ();
public WebSocketHolder(ILogger<WebSocketHolder> logger,
IHostApplicationLifetime applicationLifetime,
IHttpContextAccessor httpContext)
{
this.logger = logger;
applicationLifetime.ApplicationStopping.Register(OnShutdown);
this.httpContext = httpContext;
}
private void OnShutdown()
{
source.Cancel();
}
public async Task AddAsync(HttpContext context)
{
WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync();
if(clients.TryAdd(CreateId(), webSocket))
{
await EchoAsync(webSocket);
}
}
private string CreateId()
{
return Guid.NewGuid().ToString();
}
private async Task EchoAsync(WebSocket webSocket)
{
try
{
// for sending data
byte[] buffer = new byte[1024 * 4];
while(true)
{
string? token = this.httpContext.HttpContext?.Session?.GetString("user-token");
string? userId = this.httpContext.HttpContext?.User?.Identity?.Name;
bool? authenticated = this.httpContext.HttpContext?.User?.Identity?.IsAuthenticated;
logger.LogDebug($"Echo Token: {token} User: {userId} auth?: {authenticated}");
WebSocketReceiveResult result = await webSocket.ReceiveAsync(
new ArraySegment<byte>(buffer), source.Token);
if(result.CloseStatus.HasValue)
{
await webSocket.CloseAsync(result.CloseStatus.Value,
result.CloseStatusDescription, source.Token);
clients.TryRemove(clients.First(w => w.Value == webSocket));
webSocket.Dispose();
break;
}
// Send to all clients
foreach(var c in clients)
{
if(c.Value == webSocket)
{
continue;
}
await c.Value.SendAsync(
new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType,
result.EndOfMessage, source.Token);
}
}
}
catch(OperationCanceledException ex)
{
logger.LogError($"Exception {ex.Message}");
}
}
}
}
한 가지 중요한 것은 WebSocket 연결을 닫을 때까지 세션 값, 쿠키 값 및 로그인 상태가 유지된다는 것입니다.
자원
Reference
이 문제에 관하여([ASP.NET Core]WebSocket 사용해보기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/masanori_msl/aspnet-coretry-websocket-4fhj텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)