做过后端的比较熟悉,CAP面板有个界面,可以通过域名加cap访问:
但是这个面板直接通过url就可以访问了。
Hangfire Dashboard有自己的面板,可以使用用户名和密码做简单的认证。
LogDashboard也有自己的面板,可以使用用户名和密码做简单的认证。
如下图:
但是CAP的面板是裸露的,没有直接的认证功能。
官方提供了文档,但是没有简单的用户名和密码的认证示例。
https://cap.dotnetcore.xyz/user-guide/zh/monitoring/dashboard/
简单到不知道它在表达什么:
于是只能自己摸索了。
CAP面板引用的包:
DotNetCore.CAP.Dashboard
认证需要另外引用一个包:
Microsoft.AspNetCore.Authentication
然后创建自己的认证处理器:
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;namespace Extensions
{/// <summary>/// 自定义面板认证/// </summary>public static class MyAuthDefaults{/// <summary>/// 自定义面板认证-协议名/// </summary>public const string Policy = "MyAuthPolicy";/// <summary>/// 自定义面板认证-方案名/// </summary>public const string Scheme = "MyAuthScheme";}/// <summary>/// 自定义面板认证配置/// </summary>public class MyAuthSchemeOptions : AuthenticationSchemeOptions { }/// <summary>/// 自定义面板认证处理器/// </summary>public class MyAuthHandler : AuthenticationHandler<MyAuthSchemeOptions>{/// <summary>/// 自定义面板认证处理器/// </summary>public MyAuthHandler(IOptionsMonitor<MyAuthSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock){}/// <summary>/// 自定义面板认证-验证/// </summary>protected override async Task<AuthenticateResult> HandleAuthenticateAsync(){AuthenticateResult authResult = null;if (!Request.Headers.ContainsKey("Authorization")){authResult = AuthenticateResult.NoResult();return await Task.FromResult(authResult);}try{var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);var credentialsBytes = Convert.FromBase64String(authHeader.Parameter);var credentials = Encoding.UTF8.GetString(credentialsBytes).Split(':');var username = credentials[0];var password = credentials[1];if (username != "admin" || password != "123456"){authResult = AuthenticateResult.Fail("Invalid Username or Password");return await Task.FromResult(authResult);}var claims = new[] {new Claim(ClaimTypes.NameIdentifier, username),new Claim(ClaimTypes.Name, username),};var identity = new ClaimsIdentity(claims, Scheme.Name);var principal = new ClaimsPrincipal(identity);var ticket = new AuthenticationTicket(principal, Scheme.Name);authResult = AuthenticateResult.Success(ticket);return await Task.FromResult(authResult);}catch (Exception ex){authResult = AuthenticateResult.Fail("Invalid Authorization Header");return await Task.FromResult(authResult);}}/// <summary>/// 自定义面板认证-变动/// </summary>protected override Task HandleChallengeAsync(AuthenticationProperties properties){Response.StatusCode = 401;Response.Headers["WWW-Authenticate"] = $"Basic realm=\"{Scheme.Name}\", charset=\"UTF-8\"";return base.HandleChallengeAsync(properties);}}
}
代码里面设置了默认账号:
admin/123456
如果账号来自config配置,则需要做如下修改:
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;namespace Extensions
{/// <summary>/// 自定义面板认证/// </summary>public static class MyAuthDefaults{/// <summary>/// 自定义面板认证-协议名/// </summary>public const string Policy = "MyAuthPolicy";/// <summary>/// 自定义面板认证-方案名/// </summary>public const string Scheme = "MyAuthScheme";}/// <summary>/// 自定义面板认证配置/// </summary>public class MyAuthSchemeOptions : AuthenticationSchemeOptions { }/// <summary>/// 自定义面板认证处理器/// </summary>public class MyAuthHandler : AuthenticationHandler<MyAuthSchemeOptions>{private readonly IConfiguration _configuration;/// <summary>/// 自定义面板认证处理器/// </summary>public MyAuthHandler(IOptionsMonitor<MyAuthSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IConfiguration configuration) : base(options, logger, encoder, clock){_configuration = configuration;}/// <summary>/// 自定义面板认证-验证/// </summary>protected override async Task<AuthenticateResult> HandleAuthenticateAsync(){AuthenticateResult authResult = null;if (!Request.Headers.ContainsKey("Authorization")){authResult = AuthenticateResult.NoResult();return await Task.FromResult(authResult);}try{var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);var credentialsBytes = Convert.FromBase64String(authHeader.Parameter);var credentials = Encoding.UTF8.GetString(credentialsBytes).Split(':');var username = credentials[0];var password = credentials[1];var checkUser = _configuration["Auth:User"];var checkPwd = _configuration["Auth:Pwd"];if (username != checkUser || password != checkPwd){authResult = AuthenticateResult.Fail("Invalid Username or Password");return await Task.FromResult(authResult);}var claims = new[] {new Claim(ClaimTypes.NameIdentifier, username),new Claim(ClaimTypes.Name, username),};var identity = new ClaimsIdentity(claims, Scheme.Name);var principal = new ClaimsPrincipal(identity);var ticket = new AuthenticationTicket(principal, Scheme.Name);authResult = AuthenticateResult.Success(ticket);return await Task.FromResult(authResult);}catch (Exception ex){authResult = AuthenticateResult.Fail("Invalid Authorization Header");return await Task.FromResult(authResult);}}/// <summary>/// 自定义面板认证-变动/// </summary>protected override Task HandleChallengeAsync(AuthenticationProperties properties){Response.StatusCode = 401;Response.Headers["WWW-Authenticate"] = $"Basic realm=\"{Scheme.Name}\", charset=\"UTF-8\"";return base.HandleChallengeAsync(properties);}}
}
然后创建一个异常处理中间件里面,里面专门针对cap面板的路径做处理:
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Extensions;namespace Middleware
{/// <summary>/// 全局异常处理 中间件/// </summary>public class ExceptionHandlerMiddleware{private readonly RequestDelegate next;private readonly ILogger logger;/// <summary>/// 全局异常处理 中间件/// </summary>public ExceptionHandlerMiddleware(RequestDelegate next, ILoggerFactory loggerFactory){this.next = next;logger = loggerFactory.CreateLogger("globeException");}/// <summary>/// 全局异常处理 中间件/// </summary>public async Task Invoke(HttpContext context){try{//CAP面板认证if (context.Request.Path.ToString().ToLower().Contains("/cap/")){var authResult = await context.AuthenticateAsync(MyAuthDefaults.Scheme);if (authResult.Succeeded == false) //认证失败{await context.ChallengeAsync(MyAuthDefaults.Scheme);return;}context.User = authResult.Principal; //成功后用户信息返回await next(context); // 如果需要继续处理请求,可以调用下一个中间件return;}else{await next(context);}}catch (Exception ex){string exStr = ex.Message;}}}
}
添加针对面板的扩展:
using Extensions;namespace Extensions
{public static class CapDashboardExtensions{/// <summary>/// 带认证的面板/// </summary>public static void AddCapDashboard(this IServiceCollection services, WebApplicationBuilder builder){services.AddAuthentication(MyAuthDefaults.Scheme).AddScheme<MyAuthSchemeOptions, MyAuthHandler>(MyAuthDefaults.Scheme, options => { });services.AddAuthorization(options =>{options.AddPolicy(MyAuthDefaults.Policy, policy =>{policy.AddAuthenticationSchemes(MyAuthDefaults.Scheme).RequireAuthenticatedUser();});});services.AddCors(options =>{options.AddDefaultPolicy(builder =>{builder.AllowCredentials().AllowAnyHeader().AllowAnyMethod();});});services.AddCap(options =>{options.FailedRetryInterval = 60;options.FailedRetryCount = 50;//需要认证options.UseDashboard(d =>{d.PathMatch = "/Cap";d.UseChallengeOnAuth = true;d.DefaultChallengeScheme = MyAuthDefaults.Scheme;});});}}
}
最后在Program里面加上面板的扩展:
var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;
services.AddCapDashboard(builder); //添加Cap面板var app = builder.Build();
app.UseCors();
app.UseAuthentication(); // 启用认证中间件
app.UseAuthorization();
然后项目启动后,域名加上cap:
http://127.0.0.1:80/cap
就可以进入认证界面了:
大功搞成!输入代码里的账号,就可以进入CAP面板了:
因为网上找不到简单的CAP认证实现功能,所以这个功能摸索着开发了好久。
创作不易,给点鼓励,谢谢!