Я работаю с SignalR и хабами в asp.net core 2.0. Для каждого хаба и вызова метода я хочу иметь возможность настраивать разные аутентификации. На данный момент у меня есть два варианта авторизации,
- На основе подписанного токена JWT RSA
- На основе локального IP-адреса.
Когда я добавляю [Authorize(CustomDefaults.Server)]
к концентратору, клиент не может подключиться к концентратору и возвращает ошибку 404.
Что я хочу сделать, так это то, что когда клиент подключается к концентратору, его токен должен проверяться открытым ключом, хранящимся на сервере. Когда сервер подключается к концентратору, он должен проверить, находится ли он в локальной сети.
Например, у меня есть хаб:
[Authorize(CustomDefaults.Server)]
public class ChatHub : Hub
{
public async Task Send(string message)
{
await Clients.All.SendAsync("Receive", message);
}
}
В моем StartUp.cs у меня есть следующее:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseAuthentication();
app.UseSignalR(routes => { routes.MapHub<ChatHub>("/chat"); });
}
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication();
services.AddSignalR();
services.AddAuthentication(CustomDefaults.Server)
.AddServerAuthentication<ServerAuthenticationService>(o =>
{
o.CertificateKeyProvider = new CertificateProvider(_settings);
});
services.AddAuthentication(CustomNuDefaults.Customers)
.AddCustomerAuthentication<CustomerAuthenticationService>(o =>
{
o.CertificateKeyProvider = new CertificateProvider(_settings);
});
}
Два пользовательских обработчика:
// this handler checks the if the given token is valid by validating it with the public key stored on the server.
public class IsAuthorizedCustomerClientHandler : AuthenticationHandler<CustomOptions>
{
private readonly ICustomerAuthenticationService _customerAuthenticationService;
public IsAuthorizedCustomerClientHandler(IOptionsMonitor<CustomNuOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, ICustomerAuthenticationService customerAuthenticationService) : base(options, logger, encoder, clock)
{
_customerAuthenticationService = customerAuthenticationService;
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
var token = Request.Query["token"].ToString();
if (string.IsNullOrEmpty(token))
{
return Task.FromResult(AuthenticateResult.Fail("No token provided for authentication"));
}
var isValidUser = _customerAuthenticationService.IsAuthorizedCustomerAsync(Options.CertificateKeyProvider, token).Result;
if (!isValidUser.Item1)
{
return Task.FromResult(AuthenticateResult.Fail(isValidUser.Item2));
}
var claims = new Claim[] { };
var identity = new ClaimsIdentity(claims, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return Task.FromResult(AuthenticateResult.Success(ticket));
}
}
// This handler checks if the client has a local Ip address. If true it should be let thrue.
public class IsAuthorizedServerClientHandler : AuthenticationHandler<CustomOptions>, IAuthorizationRequirement
{
private readonly IServerAuthenticationService _serverAuthenticationService;
public IsAuthorizedServerClientHandler(IOptionsMonitor<CustomOptions> options, ILoggerFactory logger,
UrlEncoder encoder, ISystemClock clock,
IServerAuthenticationService serverAuthenticationService) : base(options, logger, encoder,
clock)
{
_serverAuthenticationService = serverAuthenticationService;
}
protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
{
Response.Headers["WWW-Authenticate"] = $"Gaat fout";
await base.HandleChallengeAsync(properties);
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
var requestIp = Context.Request.HttpContext.Connection.LocalIpAddress;
bool isAuthorizedServer = _serverAuthenticationService.IsAuthorizedServerAsync(requestIp).Result;
if (!isAuthorizedServer)
return Task.FromResult(AuthenticateResult.Fail("Server is not autherized"));
var claims = new Claim[] { };
var identity = new ClaimsIdentity(claims, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
var authenticationResult = AuthenticateResult.Success(ticket);
return Task.FromResult(authenticationResult);
}
}
Службы, которыми пользуется ручка.
public interface ICustomerAuthenticationService
{
Task<Tuple<bool, string>> IsAuthorizedCustomerAsync(ICertificateKeyProvider certificateKeyProvider,
string encodedToken);
}
public interface IServerAuthenticationService
{
Task<bool> IsAuthorizedServerAsync(IPAddress serverIp);
}
public class CustomerAuthenticateionService : ICustomerAuthenticationService
{
public Task<Tuple<bool, string>> IsAuthorizedCustomerAsync(ICertificateKeyProvider certificateKeyProvider,
string encodedToken)
{
SignedToken signedToken = new SignedToken(certificateKeyProvider);
Tuple<bool, string> validationResult = signedToken.ValidateToken(encodedToken);
return Task.FromResult(validationResult);
}
}
public class ServerAuthenticationService : IServerAuthenticationService
{
public Task<bool> IsAuthorizedServerAsync(IPAddress serverIp)
{
if (IPAddress.IsLoopback(serverIp))
return Task.FromResult(true);
return Task.FromResult(false);
}
}
Опции и методы расширения для компоновщика приложений.
// the custom options used to store information about the private key and public key.
public class CustomOptions : AuthenticationSchemeOptions, IOptions<CustomOptions>
{
public ICertificateKeyProvider CertificateKeyProvider { get; set; }
public CustomerOptions Value { get; }
}
// the defaults used for scheme names.
public class CustomDefaults
{
public const string Client = nameof(Client);
public const string Server = nameof(Server);
}
public class PostConfigureOptions : IPostConfigureOptions<CustomOptions>
{
public void PostConfigure(string name, CustomOptions options)
{
if (options.CertificateKeyProvider == null)
{
throw new InvalidOperationException("Certificate provider must be provided!");
}
}
}
// extentions for the app builder so that or custom implementation will be registered in asp.net core.
public static class TokenAppBuilderExtensions
{
public static AuthenticationBuilder AddCustomerAuthentication<TAuthService>(
this AuthenticationBuilder builder, Action<CustomOptions> configureOptions)
where TAuthService : class, ICustomerAuthenticationService
{
return AddCustomerAuthentication<TAuthService>(builder, Defaults.Client,
configureOptions);
}
public static AuthenticationBuilder AddCustomerAuthentication<TAuthService>(
this AuthenticationBuilder builder, string authenticationScheme, Action<Options> configureOptions)
where TAuthService : class, ICustomerAuthenticationService
{
builder.Services.AddSingleton<IPostConfigureOptions<CustomOptions>, PostConfigureOptions>();
builder.Services.AddTransient<ICustomerAuthenticationService, TAuthService>();
builder.Services.AddTransient<IsAuthorizedCustomerClientHandler>();
return builder.AddScheme<CustomOptions, IsAuthorizedCustomerClientHandler>(
authenticationScheme, configureOptions);
}
public static AuthenticationBuilder AddServerAuthentication<TAuthService>(
this AuthenticationBuilder builder, Action<CustomOptions> configureOptions)
where TAuthService : class, IServerAuthenticationService
{
return AddServerAuthentication<TAuthService>(builder, Defaults.Server,
configureOptions);
}
public static AuthenticationBuilder AddServerAuthentication<TAuthService>(
this AuthenticationBuilder builder, string authenticationScheme, Action<CustomOptions> configureOptions)
where TAuthService : class, IServerAuthenticationService
{
builder.Services.TryAddEnumerable(ServiceDescriptor
.Singleton<IPostConfigureOptions<CustomOptions>, PostConfigureOptions>());
builder.Services.AddTransient<IServerAuthenticationService, TAuthService>();
return builder.AddScheme<CustomOptions, IsAuthorizedServerClientHandler>(authenticationScheme,
(string) null, configureOptions);
}
}
}