fix chore
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
|
||||
using AntDesign;
|
||||
using Atomx.Admin.Client.Models;
|
||||
using Atomx.Admin.Client.Validators;
|
||||
using Atomx.Admin.Services;
|
||||
using Atomx.Admin.Utils;
|
||||
using Atomx.Common.Entities;
|
||||
using Atomx.Common.Models;
|
||||
using Atomx.Common.Utils;
|
||||
using Atomx.Data;
|
||||
@@ -18,6 +20,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -56,13 +59,15 @@ namespace Atomx.Admin.Controllers
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> Login(LoginModel model)
|
||||
{
|
||||
var result = new ApiResult<AuthResponse>();
|
||||
|
||||
var validator = new LoginModelValidator();
|
||||
var validation = validator.Validate(model);
|
||||
|
||||
if (!validation.IsValid)
|
||||
{
|
||||
var message = validation.Errors.FirstOrDefault()?.ErrorMessage;
|
||||
var result = new ApiResult<string>().IsFail(message ?? string.Empty, null);
|
||||
result = result.IsFail(message ?? string.Empty, null);
|
||||
return new JsonResult(result);
|
||||
}
|
||||
|
||||
@@ -86,12 +91,12 @@ namespace Atomx.Admin.Controllers
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
var result = new ApiResult<string>().IsFail("用户不存在", null);
|
||||
result = result.IsFail("用户不存在", null);
|
||||
return new JsonResult(result);
|
||||
}
|
||||
if (user.Password != model.Password.ToMd5Password())
|
||||
{
|
||||
var result = new ApiResult<string>().IsFail("账号密码不正确", null);
|
||||
result = result.IsFail("账号密码不正确", null);
|
||||
return new JsonResult(result);
|
||||
}
|
||||
|
||||
@@ -119,19 +124,36 @@ namespace Atomx.Admin.Controllers
|
||||
Audience = audience
|
||||
};
|
||||
|
||||
var tokenString = tokenHandler.WriteToken(tokenHandler.CreateToken(tokenDescriptor));
|
||||
|
||||
var loginResult = new ApiResult<string>().IsSuccess(tokenString);
|
||||
|
||||
var tokenString = tokenHandler.WriteToken(tokenHandler.CreateToken(tokenDescriptor));
|
||||
var refreshToken = GenerateRefreshToken();
|
||||
result.Data = new AuthResponse
|
||||
{
|
||||
Token = tokenString,
|
||||
RefreshToken = refreshToken,
|
||||
TokenExpiry = tokenDescriptor.Expires!.Value
|
||||
};
|
||||
|
||||
var refreshTokenItem = new RefreshToken
|
||||
{
|
||||
Token = refreshToken,
|
||||
UserId = user.Id,
|
||||
ExpiresTime = tokenDescriptor.Expires.Value,
|
||||
Ip = _identityService.GetClientIp(),
|
||||
IssuedTime = DateTime.UtcNow,
|
||||
IsRevoked = false,
|
||||
UserAgent = _identityService.GetUserAgent()
|
||||
};
|
||||
|
||||
user.LastLogin = DateTime.UtcNow;
|
||||
user.LastIp = _identityService.GetClientIp();
|
||||
user.LoginCount++;
|
||||
_dbContext.Admins.Update(user);
|
||||
_dbContext.RefreshTokens.Add(refreshTokenItem);
|
||||
_dbContext.SaveChanges();
|
||||
|
||||
//((PersistingRevalidatingAuthenticationStateProvider) _authenticationStateProvider).
|
||||
|
||||
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity));
|
||||
|
||||
return new JsonResult(loginResult);
|
||||
return new JsonResult(result);
|
||||
|
||||
}
|
||||
|
||||
@@ -146,5 +168,13 @@ namespace Atomx.Admin.Controllers
|
||||
await HttpContext.SignOutAsync();
|
||||
return new JsonResult(new ApiResult<string>());
|
||||
}
|
||||
|
||||
private string GenerateRefreshToken()
|
||||
{
|
||||
var randomNumber = new byte[32];
|
||||
using var rng = RandomNumberGenerator.Create();
|
||||
rng.GetBytes(randomNumber);
|
||||
return Convert.ToBase64String(randomNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
using Atomx.Common.Constant;
|
||||
using Atomx.Common.Models;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.ResponseCompression;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Atomx.Admin.Extensions
|
||||
{
|
||||
@@ -14,7 +16,7 @@ namespace Atomx.Admin.Extensions
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <param name="Configuration"></param>
|
||||
public static void AddAuthorize(this IServiceCollection services, IConfiguration Configuration)
|
||||
public static void AddAuthorize(this IServiceCollection services, IConfiguration Configuration, IWebHostEnvironment environment)
|
||||
{
|
||||
var jwtSetting = Configuration.GetSection("Authentication:JwtBearer").Get<JwtSetting>();
|
||||
if (jwtSetting == null)
|
||||
@@ -28,6 +30,7 @@ namespace Atomx.Admin.Extensions
|
||||
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
|
||||
{
|
||||
options.ClaimsIssuer = jwtSetting.Issuer;
|
||||
options.RequireHttpsMetadata = !environment.IsDevelopment(); //是否要求HTTPS,生产环境建议为true
|
||||
options.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuer = true,
|
||||
@@ -36,17 +39,46 @@ namespace Atomx.Admin.Extensions
|
||||
ValidateIssuerSigningKey = true,
|
||||
ValidAudience = jwtSetting.Audience,//Audience
|
||||
ValidIssuer = jwtSetting.Issuer,
|
||||
ClockSkew = TimeSpan.FromMinutes(Convert.ToDouble(jwtSetting.ClockSkew)),
|
||||
ClockSkew = TimeSpan.FromMinutes(Convert.ToDouble(jwtSetting.ClockSkew)), //有效期时间偏差
|
||||
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSetting.SecurityKey))
|
||||
};
|
||||
options.Events = new JwtBearerEvents
|
||||
{
|
||||
// SignalR JWT支持
|
||||
OnMessageReceived = context =>
|
||||
{
|
||||
//从查询字符串中获取令牌(如果存在)
|
||||
var accessToken = context.Request.Query["access_token"];
|
||||
var path = context.HttpContext.Request.Path;
|
||||
|
||||
if (!string.IsNullOrEmpty(accessToken) && (path.StartsWithSegments("/hubs") || path.StartsWithSegments("/notification")))
|
||||
{
|
||||
context.Token = accessToken;
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
|
||||
OnChallenge = context =>
|
||||
{
|
||||
var absoluteUri = $"{context.Request.Scheme}://{context.Request.Host}{context.Request.Path}{context.Request.QueryString}";
|
||||
context.HandleResponse();
|
||||
context.Response.Redirect($"/account/login?returnUrl={Uri.EscapeDataString(absoluteUri)}");
|
||||
return Task.CompletedTask;
|
||||
|
||||
|
||||
//context.HandleResponse();
|
||||
//context.Response.StatusCode = StatusCodes.Status401Unauthorized;
|
||||
//context.Response.ContentType = "application/json";
|
||||
|
||||
//var result = JsonSerializer.Serialize(new
|
||||
//{
|
||||
// StatusCode = 401,
|
||||
// Message = "未授权访问",
|
||||
// Error = context.Error,
|
||||
// ErrorDescription = context.ErrorDescription
|
||||
//});
|
||||
|
||||
//return context.Response.WriteAsync(result);
|
||||
},
|
||||
|
||||
OnAuthenticationFailed = context =>
|
||||
@@ -58,6 +90,21 @@ namespace Atomx.Admin.Extensions
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
//OnTokenValidated = async context =>
|
||||
//{
|
||||
// var userService = context.HttpContext.RequestServices.GetRequiredService<IUserService>();
|
||||
// var userId = context.Principal?.FindFirst("sub")?.Value;
|
||||
|
||||
// if (userId != null)
|
||||
// {
|
||||
// var user = await userService.GetUserByIdAsync(userId);
|
||||
// if (user == null || !user.IsActive)
|
||||
// {
|
||||
// context.Fail("用户不存在或已被禁用");
|
||||
// }
|
||||
// }
|
||||
//},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -85,6 +132,34 @@ namespace Atomx.Admin.Extensions
|
||||
// policy.Requirements.Add(new PermissionRequirement(PermissionConstants.Users.Edit));
|
||||
//});
|
||||
});
|
||||
|
||||
|
||||
|
||||
//// HTTP客户端工厂
|
||||
//services.AddHttpClient("ApiClient")
|
||||
// .AddTransientHttpErrorPolicy(policy =>
|
||||
// policy.WaitAndRetryAsync(3, retryAttempt =>
|
||||
// TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))))
|
||||
// .AddTransientHttpErrorPolicy(policy =>
|
||||
// policy.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
|
||||
|
||||
|
||||
//// API版本控制
|
||||
//services.AddApiVersioning(options =>
|
||||
//{
|
||||
// options.DefaultApiVersion = new Microsoft.AspNetCore.Mvc.ApiVersion(1, 0);
|
||||
// options.AssumeDefaultVersionWhenUnspecified = true;
|
||||
// options.ReportApiVersions = true;
|
||||
//});
|
||||
|
||||
|
||||
//// 添加响应压缩
|
||||
//services.AddResponseCompression(options =>
|
||||
//{
|
||||
// options.EnableForHttps = true;
|
||||
// options.Providers.Add<BrotliCompressionProvider>();
|
||||
// options.Providers.Add<GzipCompressionProvider>();
|
||||
//});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
45
Atomx.Admin/Atomx.Admin/Extensions/RateLimiterExtension.cs
Normal file
45
Atomx.Admin/Atomx.Admin/Extensions/RateLimiterExtension.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System.Threading.RateLimiting;
|
||||
|
||||
namespace Atomx.Admin.Extensions
|
||||
{
|
||||
public static class RateLimiterExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 添加限速
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <param name="Configuration"></param>
|
||||
public static void AddRateLimiter(this IServiceCollection services, IConfiguration Configuration, IWebHostEnvironment environment)
|
||||
{
|
||||
// 速率限制
|
||||
services.AddRateLimiter(limiterOptions =>
|
||||
{
|
||||
limiterOptions.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
|
||||
|
||||
// 全局限制
|
||||
limiterOptions.AddPolicy("global", context =>
|
||||
RateLimitPartition.GetFixedWindowLimiter(
|
||||
partitionKey: context.Connection.RemoteIpAddress?.ToString() ?? "unknown",
|
||||
factory: _ => new FixedWindowRateLimiterOptions
|
||||
{
|
||||
PermitLimit = 100,
|
||||
Window = TimeSpan.FromMinutes(1),
|
||||
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
|
||||
QueueLimit = 2
|
||||
}));
|
||||
|
||||
// 登录限制
|
||||
limiterOptions.AddPolicy("login", context =>
|
||||
RateLimitPartition.GetFixedWindowLimiter(
|
||||
partitionKey: $"{context.Connection.RemoteIpAddress}_{context.Request.Path}",
|
||||
factory: _ => new FixedWindowRateLimiterOptions
|
||||
{
|
||||
PermitLimit = 5,
|
||||
Window = TimeSpan.FromMinutes(15),
|
||||
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
|
||||
QueueLimit = 0
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using Atomx.Admin.Middlewares;
|
||||
|
||||
namespace Atomx.Admin.Extensions
|
||||
{
|
||||
public static class SecurityHeadersExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 安全头
|
||||
/// </summary>
|
||||
/// <param name="builder"></param>
|
||||
/// <returns></returns>
|
||||
public static IApplicationBuilder UseSecurityHeaders(this IApplicationBuilder builder)
|
||||
{
|
||||
return builder.UseMiddleware<SecurityHeadersMiddleware>();
|
||||
}
|
||||
}
|
||||
}
|
||||
33
Atomx.Admin/Atomx.Admin/Extensions/SignalRExtension.cs
Normal file
33
Atomx.Admin/Atomx.Admin/Extensions/SignalRExtension.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
namespace Atomx.Admin.Extensions
|
||||
{
|
||||
public static class SignalRExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// 添加身份验证服务
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <param name="Configuration"></param>
|
||||
public static void AddSignalR(this IServiceCollection services, IConfiguration Configuration, IWebHostEnvironment environment)
|
||||
{
|
||||
// SignalR
|
||||
//services.AddSignalR(options =>
|
||||
//{
|
||||
// options.EnableDetailedErrors = environment.IsDevelopment();
|
||||
// options.ClientTimeoutInterval = TimeSpan.FromSeconds(30);
|
||||
// options.HandshakeTimeout = TimeSpan.FromSeconds(15);
|
||||
// options.KeepAliveInterval = TimeSpan.FromSeconds(10);
|
||||
// options.MaximumReceiveMessageSize = 1024 * 1024; // 1MB
|
||||
|
||||
// if (!environment.IsDevelopment())
|
||||
// {
|
||||
// options.StreamBufferCapacity = 10;
|
||||
// }
|
||||
//})
|
||||
//.AddMessagePackProtocol()
|
||||
//.AddStackExchangeRedis(redisConnectionString, options =>
|
||||
//{
|
||||
// options.Configuration.ChannelPrefix = "BlazorAuthApp:SignalR";
|
||||
//});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Atomx.Admin.Middlewares
|
||||
{
|
||||
public class ExceptionHandlingMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly ILogger<ExceptionHandlingMiddleware> _logger;
|
||||
private readonly IWebHostEnvironment _environment;
|
||||
|
||||
public ExceptionHandlingMiddleware(
|
||||
RequestDelegate next,
|
||||
ILogger<ExceptionHandlingMiddleware> logger,
|
||||
IWebHostEnvironment environment)
|
||||
{
|
||||
_next = next;
|
||||
_logger = logger;
|
||||
_environment = environment;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _next(context);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await HandleExceptionAsync(context, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleExceptionAsync(HttpContext context, Exception exception)
|
||||
{
|
||||
_logger.LogError(exception, "未处理的异常: {Message}", exception.Message);
|
||||
|
||||
var response = context.Response;
|
||||
response.ContentType = "application/json";
|
||||
|
||||
var problemDetails = new ProblemDetails
|
||||
{
|
||||
Title = "发生错误",
|
||||
Status = (int)HttpStatusCode.InternalServerError,
|
||||
Instance = context.Request.Path
|
||||
};
|
||||
|
||||
// 根据异常类型设置状态码
|
||||
switch (exception)
|
||||
{
|
||||
case UnauthorizedAccessException:
|
||||
problemDetails.Status = (int)HttpStatusCode.Unauthorized;
|
||||
problemDetails.Title = "未授权访问";
|
||||
break;
|
||||
case KeyNotFoundException:
|
||||
problemDetails.Status = (int)HttpStatusCode.NotFound;
|
||||
problemDetails.Title = "资源未找到";
|
||||
break;
|
||||
case ArgumentException:
|
||||
case InvalidOperationException:
|
||||
problemDetails.Status = (int)HttpStatusCode.BadRequest;
|
||||
problemDetails.Title = "无效请求";
|
||||
break;
|
||||
}
|
||||
|
||||
// 开发环境包含详细错误信息
|
||||
if (_environment.IsDevelopment())
|
||||
{
|
||||
problemDetails.Extensions.Add("exception", exception.Message);
|
||||
problemDetails.Extensions.Add("stackTrace", exception.StackTrace);
|
||||
problemDetails.Extensions.Add("innerException", exception.InnerException?.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
problemDetails.Detail = "处理您的请求时发生错误。请稍后重试。";
|
||||
}
|
||||
|
||||
response.StatusCode = problemDetails.Status.Value;
|
||||
await response.WriteAsync(JsonSerializer.Serialize(problemDetails));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
namespace Atomx.Admin.Middlewares
|
||||
{
|
||||
public class SecurityHeadersMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly IWebHostEnvironment _environment;
|
||||
|
||||
public SecurityHeadersMiddleware(RequestDelegate next, IWebHostEnvironment environment)
|
||||
{
|
||||
_next = next;
|
||||
_environment = environment;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context)
|
||||
{
|
||||
// 添加安全头
|
||||
if (!context.Response.HasStarted)
|
||||
{
|
||||
var headers = context.Response.Headers;
|
||||
|
||||
// CSP策略
|
||||
if (!_environment.IsDevelopment())
|
||||
{
|
||||
headers.Append("Content-Security-Policy",
|
||||
"default-src 'self'; " +
|
||||
"script-src 'self' 'unsafe-inline' 'unsafe-eval'; " +
|
||||
"style-src 'self' 'unsafe-inline'; " +
|
||||
"img-src 'self' data: https:; " +
|
||||
"font-src 'self'; " +
|
||||
"connect-src 'self' wss:; " +
|
||||
"frame-ancestors 'none';");
|
||||
}
|
||||
|
||||
// 其他安全头
|
||||
headers.Append("X-Content-Type-Options", "nosniff");
|
||||
headers.Append("X-Frame-Options", "DENY");
|
||||
headers.Append("X-XSS-Protection", "1; mode=block");
|
||||
headers.Append("Referrer-Policy", "strict-origin-when-cross-origin");
|
||||
headers.Append("Permissions-Policy",
|
||||
"camera=(), microphone=(), geolocation=(), interest-cohort=()");
|
||||
|
||||
// HSTS(在生产环境中启用)
|
||||
if (!_environment.IsDevelopment())
|
||||
{
|
||||
headers.Append("Strict-Transport-Security",
|
||||
"max-age=31536000; includeSubDomains; preload");
|
||||
}
|
||||
}
|
||||
|
||||
await _next(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,9 +14,12 @@ using Blazored.LocalStorage;
|
||||
using Mapster;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Scalar.AspNetCore;
|
||||
using Serilog;
|
||||
using StackExchange.Redis;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Text.Unicode;
|
||||
@@ -68,7 +71,10 @@ builder.Services.AddScoped<AuthHeaderHandler>();
|
||||
|
||||
builder.Services.AddHttpClientApiService(builder.Configuration["WebApi:ServerUrl"] ?? "http://localhost");
|
||||
builder.Services.AddDataService();
|
||||
builder.Services.AddAuthorize(builder.Configuration);
|
||||
builder.Services.AddAuthorize(builder.Configuration,builder.Environment);
|
||||
|
||||
// <20><><EFBFBD><EFBFBD>SignalR
|
||||
builder.Services.AddSignalR();
|
||||
|
||||
var connection = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
|
||||
builder.Services.AddDbContext<DataContext>(options => options.UseNpgsql(connection, p => p.MigrationsHistoryTable("__DbMigrationsHistory")));
|
||||
@@ -82,6 +88,8 @@ builder.Services.AddStackExchangeRedisCache(options =>
|
||||
#endregion
|
||||
});
|
||||
|
||||
|
||||
|
||||
builder.Services.AddOpenApi();
|
||||
|
||||
builder.Services.AddAntDesign();
|
||||
@@ -92,6 +100,12 @@ var app = builder.Build();
|
||||
|
||||
app.AddDataMigrate();
|
||||
|
||||
// <20><><EFBFBD><EFBFBD>HTTP<54><50><EFBFBD><EFBFBD><EFBFBD>ܵ<EFBFBD>
|
||||
app.UseForwardedHeaders(new ForwardedHeadersOptions
|
||||
{
|
||||
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
|
||||
});
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
@@ -104,7 +118,11 @@ else
|
||||
app.UseExceptionHandler("/Error", createScopeForErrors: true);
|
||||
}
|
||||
|
||||
//<2F><>ȫͷ
|
||||
app.UseSecurityHeaders();
|
||||
|
||||
// <20><>Ӧѹ<D3A6><D1B9>
|
||||
app.UseResponseCompression();
|
||||
|
||||
app.UseCors(option =>
|
||||
{
|
||||
@@ -113,7 +131,18 @@ app.UseCors(option =>
|
||||
option.AllowAnyHeader();
|
||||
});
|
||||
|
||||
app.UseStaticFiles();
|
||||
app.UseStaticFiles(new StaticFileOptions
|
||||
{
|
||||
OnPrepareResponse = ctx =>
|
||||
{
|
||||
// <20><><EFBFBD>澲̬<E6BEB2>ļ<EFBFBD>
|
||||
ctx.Context.Response.Headers.Append(
|
||||
"Cache-Control", $"public, max-age={31536000}");
|
||||
}
|
||||
});
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
//app.UseRateLimiter();
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
@@ -124,10 +153,46 @@ app.MapStaticAssets();
|
||||
app.UseMiddleware<MonitoringMiddleware>();
|
||||
|
||||
|
||||
//// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˵<EFBFBD>
|
||||
//app.MapHealthChecks("/health", new HealthCheckOptions
|
||||
//{
|
||||
// ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse,
|
||||
// Predicate = _ => true,
|
||||
// AllowCachingResponses = false
|
||||
//});
|
||||
|
||||
//app.MapHealthChecks("/health/ready", new HealthCheckOptions
|
||||
//{
|
||||
// ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse,
|
||||
// Predicate = check => check.Tags.Contains("ready")
|
||||
//});
|
||||
|
||||
//app.MapHealthChecks("/health/live", new HealthCheckOptions
|
||||
//{
|
||||
// ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse,
|
||||
// Predicate = _ => false
|
||||
//});
|
||||
|
||||
//// SignalR<6C>˵<EFBFBD>
|
||||
//app.MapHub<ChatHub>("/hubs/chat");
|
||||
//app.MapHub<NotificationHub>("/hubs/notification");
|
||||
|
||||
app.MapControllers();
|
||||
app.MapRazorComponents<App>()
|
||||
.AddInteractiveServerRenderMode()
|
||||
.AddInteractiveWebAssemblyRenderMode()
|
||||
.AddAdditionalAssemblies(typeof(Atomx.Admin.Client._Imports).Assembly);
|
||||
|
||||
//// ȷ<><C8B7><EFBFBD><EFBFBD><EFBFBD>ݿⴴ<DDBF><E2B4B4><EFBFBD><EFBFBD>Ǩ<EFBFBD><C7A8>
|
||||
//await using (var scope = app.Services.CreateAsyncScope())
|
||||
//{
|
||||
// var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
// await dbContext.Database.MigrateAsync();
|
||||
|
||||
// var seeder = scope.ServiceProvider.GetRequiredService<DatabaseSeeder>();
|
||||
// await seeder.SeedAsync();
|
||||
//}
|
||||
|
||||
|
||||
|
||||
app.Run();
|
||||
|
||||
@@ -31,6 +31,12 @@ namespace Atomx.Admin.Services
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
int GetTimeZone();
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户代理信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
string GetUserAgent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -102,5 +108,10 @@ namespace Atomx.Admin.Services
|
||||
var timeZone = _httpContextAccessor.HttpContext?.User?.Claims?.SingleOrDefault(p => p.Type == "TimeZone")?.Value ?? "0";
|
||||
return timeZone.ToInt();
|
||||
}
|
||||
|
||||
public string GetUserAgent()
|
||||
{
|
||||
return _httpContextAccessor.HttpContext?.Request.Headers["User-Agent"].FirstOrDefault() ?? "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"Issuer": "http://api.sampleapi.com",
|
||||
"Audience": "SampleApi",
|
||||
"SecurityKey": "SecurityKey23456SecurityKey23456",
|
||||
"ClockSkew": "600",
|
||||
"ClockSkew": "10", // 10分钟时钟偏差
|
||||
"AccessTokenExpirationMinutes": "60",
|
||||
"RefreshTokenExpirationMinutes": "60"
|
||||
}
|
||||
|
||||
@@ -30,11 +30,13 @@ namespace Atomx.Common.Entities
|
||||
/// <summary>
|
||||
/// 发布时间
|
||||
/// </summary>
|
||||
[Column(TypeName = "timestamptz")]
|
||||
public DateTime IssuedTime { get; set; } = DateTime.UtcNow;
|
||||
|
||||
/// <summary>
|
||||
/// 到期时间
|
||||
/// </summary>
|
||||
[Column(TypeName = "timestamptz")]
|
||||
public DateTime ExpiresTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -42,6 +44,12 @@ namespace Atomx.Common.Entities
|
||||
/// </summary>
|
||||
public bool IsRevoked { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 回收时间
|
||||
/// </summary>
|
||||
[Column(TypeName = "timestamptz")]
|
||||
public DateTime? RevokedTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户IP
|
||||
/// </summary>
|
||||
|
||||
23
Atomx.Common/Models/AuthResponse.cs
Normal file
23
Atomx.Common/Models/AuthResponse.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace Atomx.Common.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// 授权响应模型
|
||||
/// </summary>
|
||||
public class AuthResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// 令牌
|
||||
/// </summary>
|
||||
public string Token { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 刷新令牌
|
||||
/// </summary>
|
||||
public string RefreshToken { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 令牌过期时间
|
||||
/// </summary>
|
||||
public DateTime TokenExpiry { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user