@@ -2,7 +2,7 @@ | |||
<PropertyGroup> | |||
<VersionMajor>5</VersionMajor> | |||
<VersionMinor>1</VersionMinor> | |||
<VersionPatch>0</VersionPatch> | |||
<VersionPatch>1</VersionPatch> | |||
<VersionQuality></VersionQuality> | |||
<VersionPrefix>$(VersionMajor).$(VersionMinor).$(VersionPatch)</VersionPrefix> | |||
</PropertyGroup> | |||
@@ -0,0 +1,50 @@ | |||
using System.Linq; | |||
using System.Security.Claims; | |||
using System.Text.Encodings.Web; | |||
using System.Threading.Tasks; | |||
using Microsoft.AspNetCore.Authentication; | |||
using Microsoft.Extensions.Logging; | |||
using Microsoft.Extensions.Options; | |||
namespace Sample.Dashboard.Auth | |||
{ | |||
public class MyDashboardAuthenticationSchemeOptions : AuthenticationSchemeOptions | |||
{ | |||
} | |||
public class MyDashboardAuthenticationHandler : AuthenticationHandler<MyDashboardAuthenticationSchemeOptions> | |||
{ | |||
public MyDashboardAuthenticationHandler(IOptionsMonitor<MyDashboardAuthenticationSchemeOptions> options, | |||
ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) | |||
{ | |||
options.CurrentValue.ForwardChallenge = ""; | |||
} | |||
protected override Task<AuthenticateResult> HandleAuthenticateAsync() | |||
{ | |||
var testAuthHeaderPresent = Request.Headers["X-Base-Token"].Contains("xxx"); | |||
var authResult = testAuthHeaderPresent ? AuthenticatedTestUser() : AuthenticateResult.NoResult(); | |||
return Task.FromResult(authResult); | |||
} | |||
protected override Task HandleChallengeAsync(AuthenticationProperties properties) | |||
{ | |||
Response.Headers["WWW-Authenticate"] = "MyDashboardScheme"; | |||
return base.HandleChallengeAsync(properties); | |||
} | |||
private AuthenticateResult AuthenticatedTestUser() | |||
{ | |||
var claims = new[] { new Claim(ClaimTypes.Name, "My Dashboard user") }; | |||
var identity = new ClaimsIdentity(claims, "MyDashboardScheme"); | |||
var principal = new ClaimsPrincipal(identity); | |||
var ticket = new AuthenticationTicket(principal, "MyDashboardScheme"); | |||
return AuthenticateResult.Success(ticket); | |||
} | |||
} | |||
} |
@@ -21,7 +21,7 @@ namespace Sample.Dashboard.Auth | |||
.AddAuthorization() | |||
.AddAuthentication(options => | |||
{ | |||
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; | |||
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; | |||
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; | |||
}) | |||
.AddCookie() | |||
@@ -36,7 +36,8 @@ namespace Sample.Dashboard.Auth | |||
options.Scope.Clear(); | |||
options.Scope.Add("openid"); | |||
options.Scope.Add("profile"); | |||
}); | |||
}) | |||
.AddScheme<MyDashboardAuthenticationSchemeOptions, MyDashboardAuthenticationHandler>("MyDashboardScheme",null); | |||
services.AddCors(x => | |||
{ | |||
@@ -45,13 +46,15 @@ namespace Sample.Dashboard.Auth | |||
p.WithOrigins("http://localhost:8080").AllowCredentials().AllowAnyHeader().AllowAnyMethod(); | |||
}); | |||
}); | |||
services.AddCap(cap => | |||
{ | |||
cap.UseDashboard(d => | |||
{ | |||
d.UseChallengeOnAuth = true; | |||
d.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; | |||
d.UseAuth = true; | |||
d.DefaultAuthenticationScheme = "MyDashboardScheme"; | |||
}); | |||
cap.UseMySql(_configuration.GetValue<string>("ConnectionString")); | |||
cap.UseRabbitMQ(aa => | |||
@@ -76,16 +79,16 @@ namespace Sample.Dashboard.Auth | |||
public void Configure(IApplicationBuilder app) | |||
{ | |||
app.UseAuthentication(); | |||
app.UseCors(); | |||
app.UseRouting(); | |||
app.UseAuthentication(); | |||
app.UseAuthorization(); | |||
app.UseCookiePolicy(); | |||
app.UseEndpoints(endpoints => | |||
{ | |||
endpoints.MapControllers(); | |||
endpoints.MapControllers(); | |||
}); | |||
} | |||
} | |||
} | |||
} |
@@ -3,9 +3,11 @@ | |||
using System; | |||
using System.Reflection; | |||
using System.Threading.Tasks; | |||
using DotNetCore.CAP.Dashboard; | |||
using DotNetCore.CAP.Dashboard.GatewayProxy; | |||
using DotNetCore.CAP.Dashboard.NodeDiscovery; | |||
using Microsoft.AspNetCore.Authentication; | |||
using Microsoft.AspNetCore.Builder; | |||
using Microsoft.AspNetCore.Http; | |||
using Microsoft.AspNetCore.Mvc; | |||
@@ -29,8 +31,8 @@ namespace DotNetCore.CAP | |||
var provider = app.ApplicationServices; | |||
var option = provider.GetService<DashboardOptions>(); | |||
if (option != null) | |||
var options = provider.GetService<DashboardOptions>(); | |||
if (options != null) | |||
{ | |||
if (provider.GetService<DiscoveryOptions>() != null) | |||
{ | |||
@@ -39,7 +41,7 @@ namespace DotNetCore.CAP | |||
app.UseMiddleware<UiMiddleware>(); | |||
app.Map(option.PathMatch + "/api", false, x => | |||
app.Map(options.PathMatch + "/api", false, x => | |||
{ | |||
var builder = new RouteBuilder(x); | |||
@@ -55,18 +57,24 @@ namespace DotNetCore.CAP | |||
{ | |||
builder.MapGet(getAttr.Template, async (request, response, data) => | |||
{ | |||
var actionProvider = new RouteActionProvider(request, response, data); | |||
try | |||
{ | |||
await executor.ExecuteAsync(actionProvider, null); | |||
} | |||
catch (Exception ex) | |||
{ | |||
response.StatusCode = StatusCodes.Status500InternalServerError; | |||
await response.WriteAsync(ex.Message); | |||
} | |||
}); | |||
{ | |||
if (!await Authentication(request.HttpContext, options)) | |||
{ | |||
response.StatusCode = StatusCodes.Status401Unauthorized; | |||
return; | |||
} | |||
var actionProvider = new RouteActionProvider(request, response, data); | |||
try | |||
{ | |||
await executor.ExecuteAsync(actionProvider, null); | |||
} | |||
catch (Exception ex) | |||
{ | |||
response.StatusCode = StatusCodes.Status500InternalServerError; | |||
await response.WriteAsync(ex.Message); | |||
} | |||
}); | |||
} | |||
var postAttr = method.GetCustomAttribute<HttpPostAttribute>(); | |||
@@ -74,6 +82,12 @@ namespace DotNetCore.CAP | |||
{ | |||
builder.MapPost(postAttr.Template, async (request, response, data) => | |||
{ | |||
if (!await Authentication(request.HttpContext, options)) | |||
{ | |||
response.StatusCode = StatusCodes.Status401Unauthorized; | |||
return; | |||
} | |||
var actionProvider = new RouteActionProvider(request, response, data); | |||
try | |||
{ | |||
@@ -97,6 +111,34 @@ namespace DotNetCore.CAP | |||
return app; | |||
} | |||
internal static async Task<bool> Authentication(HttpContext context, DashboardOptions options) | |||
{ | |||
if (options.UseAuth) | |||
{ | |||
var result = await context.AuthenticateAsync(options.DefaultAuthenticationScheme); | |||
if (result.Succeeded && result.Principal != null) | |||
{ | |||
context.User = result.Principal; | |||
} | |||
else | |||
{ | |||
return false; | |||
} | |||
} | |||
var isAuthenticated = context.User?.Identity?.IsAuthenticated; | |||
if (isAuthenticated == false && options.UseChallengeOnAuth) | |||
{ | |||
await context.ChallengeAsync(options.DefaultChallengeScheme); | |||
return false; | |||
} | |||
return true; | |||
} | |||
private static void CheckRequirement(IApplicationBuilder app) | |||
{ | |||
var marker = app.ApplicationServices.GetService<CapMarkerService>(); | |||
@@ -2,6 +2,7 @@ | |||
// Licensed under the MIT License. See License.txt in the project root for license information. | |||
// ReSharper disable once CheckNamespace | |||
namespace DotNetCore.CAP | |||
{ | |||
public class DashboardOptions | |||
@@ -12,17 +13,38 @@ namespace DotNetCore.CAP | |||
StatsPollingInterval = 2000; | |||
} | |||
/// <summary> | |||
/// When behind the proxy, specify the base path to allow spa call prefix. | |||
/// </summary> | |||
public string PathBase { get; set; } | |||
/// <summary> | |||
/// Path prefix to match from url path. | |||
/// </summary> | |||
public string PathMatch { get; set; } | |||
/// <summary> | |||
/// The interval the /stats endpoint should be polled with. | |||
/// </summary> | |||
public int StatsPollingInterval { get; set; } | |||
/// <summary> | |||
/// Enable authentication on dashboard request. | |||
/// </summary> | |||
public bool UseAuth { get; set; } | |||
/// <summary> | |||
/// Default scheme used for authentication. If no scheme is set, the DefaultScheme set up in AddAuthentication will be used. | |||
/// </summary> | |||
public string DefaultAuthenticationScheme { get; set; } | |||
/// <summary> | |||
/// Enable authentication challenge on dashboard request. | |||
/// </summary> | |||
public bool UseChallengeOnAuth { get; set; } | |||
/// <summary> | |||
/// Default ChallengeScheme used for Dashboard authentication. If no scheme is set, the DefaultScheme set up in AddAuthentication will be used. | |||
/// Default scheme used for authentication challenge. If no scheme is set, the DefaultChallengeScheme set up in AddAuthentication will be used. | |||
/// </summary> | |||
public string DefaultChallengeScheme { get; set; } | |||
} |
@@ -5,7 +5,6 @@ using System.Reflection; | |||
using System.Text; | |||
using System.Text.RegularExpressions; | |||
using System.Threading.Tasks; | |||
using Microsoft.AspNetCore.Authentication; | |||
using Microsoft.AspNetCore.Builder; | |||
using Microsoft.AspNetCore.Hosting; | |||
using Microsoft.AspNetCore.Http; | |||
@@ -47,11 +46,9 @@ namespace DotNetCore.CAP.Dashboard | |||
if (httpMethod == "GET" && Regex.IsMatch(path, $"^/?{Regex.Escape(_options.PathMatch)}/?index.html$", RegexOptions.IgnoreCase)) | |||
{ | |||
var isAuthenticated = httpContext.User?.Identity?.IsAuthenticated; | |||
if (isAuthenticated == false && _options.UseChallengeOnAuth) | |||
if (!await CapBuilderExtension.Authentication(httpContext, _options)) | |||
{ | |||
await httpContext.ChallengeAsync(_options.DefaultChallengeScheme); | |||
httpContext.Response.StatusCode = StatusCodes.Status401Unauthorized; | |||
return; | |||
} | |||
@@ -63,7 +60,7 @@ namespace DotNetCore.CAP.Dashboard | |||
using var sr = new StreamReader(stream); | |||
var htmlBuilder = new StringBuilder(await sr.ReadToEndAsync()); | |||
htmlBuilder.Replace("%(servicePrefix)", _options.PathMatch + "/api"); | |||
htmlBuilder.Replace("%(servicePrefix)", _options.PathBase + _options.PathMatch + "/api"); | |||
htmlBuilder.Replace("%(pollingInterval)", _options.StatsPollingInterval.ToString()); | |||
await httpContext.Response.WriteAsync(htmlBuilder.ToString(), Encoding.UTF8); | |||