我正在使用SignalR和asp.net core 2.0中的集线器。对于每个集线器和方法调用,我希望能够配置不同的身份验证。目前,我有两个授权选项,
当我将[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);
}
}
}