Switch Authentication to asynchronous streaming calls (#16)

* add base grpc service and swap auth service to streaming

* remove Authorize from hub itself

* remove unused usings

* heave files server to net 7, add exception handling in grpc auth stream

Co-authored-by: rootdarkarchon <root.darkarchon@outlook.com>
This commit is contained in:
rootdarkarchon
2022-10-13 16:55:23 +02:00
committed by GitHub
parent d37c1208fe
commit c98e2b2dd6
20 changed files with 313 additions and 159 deletions

View File

@@ -1,7 +1,7 @@
using System.Security.Claims;
using System.Text.Encodings.Web;
using MareSynchronosServer;
using MareSynchronosShared.Protos;
using MareSynchronosShared.Services;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
@@ -14,13 +14,13 @@ public class SecretKeyGrpcAuthenticationHandler : AuthenticationHandler<Authenti
{
public const string AuthScheme = "SecretKeyGrpcAuth";
private readonly AuthService.AuthServiceClient _authClient;
private readonly GrpcAuthenticationService _grpcAuthService;
private readonly IHttpContextAccessor _accessor;
public SecretKeyGrpcAuthenticationHandler(IHttpContextAccessor accessor, AuthService.AuthServiceClient authClient,
public SecretKeyGrpcAuthenticationHandler(IHttpContextAccessor accessor, GrpcAuthenticationService authClient,
IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
{
this._authClient = authClient;
this._grpcAuthService = authClient;
_accessor = accessor;
}
@@ -33,7 +33,7 @@ public class SecretKeyGrpcAuthenticationHandler : AuthenticationHandler<Authenti
var ip = _accessor.GetIpAddress();
var authResult = await _authClient.AuthorizeAsync(new AuthRequest() { Ip = ip, SecretKey = authHeader }).ConfigureAwait(false);
var authResult = await _grpcAuthService.AuthorizeAsync(ip, authHeader).ConfigureAwait(false);
if (!authResult.Success)
{
@@ -44,7 +44,7 @@ public class SecretKeyGrpcAuthenticationHandler : AuthenticationHandler<Authenti
var claims = new List<Claim>
{
new(ClaimTypes.NameIdentifier, uid),
new(ClaimTypes.NameIdentifier, uid.Uid),
new(ClaimTypes.Authentication, authHeader)
};

View File

@@ -1,5 +1,4 @@
using MareSynchronosShared.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace MareSynchronosShared.Data;

View File

@@ -1,5 +1,4 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable

View File

@@ -1,10 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MareSynchronosShared.Models;
namespace MareSynchronosShared.Models;
public class GroupBan
{

View File

@@ -5,8 +5,8 @@ option csharp_namespace = "MareSynchronosShared.Protos";
package mareservices;
service AuthService {
rpc Authorize (AuthRequest) returns (AuthReply);
rpc RemoveAuth (RemoveAuthRequest) returns (Empty);
rpc Authorize (stream AuthRequest) returns (stream AuthReply);
rpc RemoveAuth (UidMessage) returns (Empty);
rpc ClearUnauthorized (Empty) returns (Empty);
}
@@ -101,10 +101,6 @@ message FileSizeResponse {
map<string, int64> hashToFileSize = 1;
}
message RemoveAuthRequest {
string uid = 1;
}
message AuthRequest {
string ip = 1;
string secretKey = 2;
@@ -112,5 +108,5 @@ message AuthRequest {
message AuthReply {
bool success = 1;
string uid = 2;
UidMessage uid = 2;
}

View File

@@ -0,0 +1,109 @@
using System.Collections.Concurrent;
using System.Security.Cryptography;
using MareSynchronosShared.Protos;
using Microsoft.Extensions.Logging;
namespace MareSynchronosShared.Services;
public class GrpcAuthenticationService : GrpcBaseService
{
private record AuthRequestInternal
{
public AuthRequest Request { get; set; }
public long Id { get; set; }
}
private readonly AuthService.AuthServiceClient _authClient;
private readonly ConcurrentQueue<AuthRequestInternal> _requestQueue = new();
private readonly ConcurrentDictionary<long, AuthReply> _authReplies = new();
private long _requestId = 0;
public GrpcAuthenticationService(ILogger<GrpcAuthenticationService> logger, AuthService.AuthServiceClient authClient) : base(logger)
{
_authClient = authClient;
}
public async Task<AuthReply> AuthorizeAsync(string ip, string secretKey)
{
using var sha1 = SHA1.Create();
var id = Interlocked.Increment(ref _requestId);
_requestQueue.Enqueue(new AuthRequestInternal()
{
Id = id,
Request = new AuthRequest()
{
Ip = ip,
SecretKey = secretKey,
}
});
using CancellationTokenSource cts = new(TimeSpan.FromSeconds(30));
AuthReply response = null;
while (!GrpcIsFaulty && !cts.IsCancellationRequested && !_authReplies.TryRemove(id, out response))
{
await Task.Delay(10, cts.Token).ConfigureAwait(false);
}
return response ?? new AuthReply
{
Success = false,
};
}
public async Task GrpcAuthStream(CancellationToken token)
{
try
{
using var stream = _authClient.Authorize(cancellationToken: token);
while (!token.IsCancellationRequested)
{
while (_requestQueue.TryDequeue(out var request))
{
await stream.RequestStream.WriteAsync(request.Request, token).ConfigureAwait(false);
await stream.ResponseStream.MoveNext(token).ConfigureAwait(false);
_authReplies[request.Id] = stream.ResponseStream.Current;
}
await Task.Delay(10, token).ConfigureAwait(false);
}
}
catch
{
SetGrpcFaulty();
}
}
protected override Task OnGrpcRestore()
{
return Task.CompletedTask;
}
protected override Task PostStartStream()
{
return Task.CompletedTask;
}
protected override Task PreStartStream()
{
_requestQueue.Clear();
_authReplies.Clear();
return Task.CompletedTask;
}
protected override Task StartAsyncInternal(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
protected override Task StartStream(CancellationToken ct)
{
_ = GrpcAuthStream(ct);
return Task.CompletedTask;
}
protected override Task StopAsyncInternal(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}

View File

@@ -0,0 +1,129 @@
using Grpc.Core;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace MareSynchronosShared.Services;
public abstract class GrpcBaseService : IHostedService, IDisposable
{
protected GrpcBaseService(ILogger logger)
{
_logger = logger;
}
private CancellationTokenSource _faultCheckCts = new();
private CancellationTokenSource _streamCts = new();
private readonly ILogger _logger;
protected bool GrpcIsFaulty { get; private set; }
protected abstract Task StartAsyncInternal(CancellationToken cancellationToken);
protected abstract Task StopAsyncInternal(CancellationToken cancellationToken);
protected abstract Task OnGrpcRestore();
protected abstract Task PreStartStream();
protected abstract Task StartStream(CancellationToken ct);
protected abstract Task PostStartStream();
public async Task StartAsync(CancellationToken cancellationToken)
{
_ = RestartStreams();
_ = CheckGrpcFaults(_faultCheckCts.Token);
await StartAsyncInternal(cancellationToken).ConfigureAwait(false);
}
public async Task StopAsync(CancellationToken cancellationToken)
{
_faultCheckCts.Cancel();
_streamCts.Cancel();
await StopAsyncInternal(cancellationToken).ConfigureAwait(false);
}
private async Task RestartStreams()
{
_streamCts?.Cancel();
_streamCts?.Dispose();
_streamCts = new();
if (!GrpcIsFaulty)
{
try
{
await PreStartStream().ConfigureAwait(false);
await StartStream(_streamCts.Token).ConfigureAwait(false);
await PostStartStream().ConfigureAwait(false);
}
catch
{
SetGrpcFaulty();
}
}
}
protected void SetGrpcFaulty()
{
if (!GrpcIsFaulty)
{
GrpcIsFaulty = true;
_logger.LogWarning("GRPC connection is faulty");
}
}
private async Task CheckGrpcFaults(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
try
{
await CheckFaultStateAndResend().ConfigureAwait(false);
}
catch { SetGrpcFaulty(); }
await Task.Delay(250).ConfigureAwait(false);
}
}
private async Task CheckFaultStateAndResend()
{
if (GrpcIsFaulty)
{
await RestartStreams().ConfigureAwait(false);
await OnGrpcRestore().ConfigureAwait(false);
_logger.LogInformation("GRPC connection is restored");
GrpcIsFaulty = false;
}
}
protected async Task<T> InvokeOnGrpc<T>(AsyncUnaryCall<T> toExecute)
{
try
{
var result = await toExecute.ConfigureAwait(false);
await CheckFaultStateAndResend().ConfigureAwait(false);
return result;
}
catch
{
SetGrpcFaulty();
return default;
}
}
protected async Task ExecuteOnGrpc<T>(AsyncUnaryCall<T> toExecute)
{
try
{
await toExecute.ConfigureAwait(false);
await CheckFaultStateAndResend().ConfigureAwait(false);
}
catch
{
SetGrpcFaulty();
}
}
public void Dispose()
{
_streamCts?.Dispose();
_faultCheckCts?.Dispose();
}
}

View File

@@ -1,11 +1,6 @@
using MareSynchronosShared.Data;
using MareSynchronosShared.Models;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MareSynchronosShared.Utils;