Как иметь несколько обработчиков аутентификации asp.net core 2.0 - PullRequest
0 голосов
/ 02 июля 2018

Я работаю с 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);
        }
    }
}
...