如何拥有多个身份验证处理程序asp.net core 2.0

时间:2018-07-02 09:34:43

标签: c# asp.net-core signalr signalr-hub

我正在使用SignalR和asp.net core 2.0中的集线器。对于每个集线器和方法调用,我希望能够配置不同的身份验证。目前,我有两个授权选项,

  • 基于签名的RSA JWT令牌
  • 基于本地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);
        }
    }
}

0 个答案:

没有答案