minimal api 踢用户下线 实现
continuous-integration/drone/push Build is passing Details

IRC_NewDev
hang 2024-10-15 15:42:08 +08:00
parent 947d6dc6d1
commit 74a0c8923c
9 changed files with 99 additions and 48 deletions

View File

@ -149,6 +149,7 @@ builder.Services.AddMasaMinimalAPIs(options =>
options.RouteHandlerBuilder= t=> {
t.RequireAuthorization()
.AddEndpointFilter<LimitUserRequestAuthorizationEndpointFilter>()
.AddEndpointFilter<UnifiedApiResultEndpointFilter>()
.WithGroupName("Institution");
};
@ -191,27 +192,6 @@ app.UseStatusCodePages(async context =>
//app.UseExceptionHandler();
app.UseExceptionHandler(o => { });
//这里没生效原因未知官方文档也是这种写法也用了GlobalExceptionHandler 尝试还是不行怀疑框架bug
//app.UseExceptionHandler(configure =>
//{
// configure.Run(async context =>
// {
// var exceptionHandlerPathFeature = context.Features.Get<IExceptionHandlerPathFeature>();
//var ex = exceptionHandlerPathFeature?.Error;
//context.Response.ContentType = "application/json";
//if (ex != null)
//{
// var errorInfo = $"Exception: {ex.Message}[{ex.StackTrace}]" + (ex.InnerException != null ? $" InnerException: {ex.InnerException.Message}[{ex.InnerException.StackTrace}]" : "");
// await context.Response.WriteAsync(JsonConvert.SerializeObject(ResponseOutput.NotOk($"{ex?.Message}")));
// Log.Logger.Error(errorInfo);
//}
// });
//});
#region 暂时废弃
//app.UseMiddleware<MultiDiskStaticFilesMiddleware>();

View File

@ -13,24 +13,10 @@ namespace IRaCIS.Core.Application.Filter;
public class LimitUserRequestAuthorization : IAsyncAuthorizationFilter
public class LimitUserRequestAuthorization(
IFusionCache _fusionCache, IUserInfo _userInfo, IStringLocalizer _localizer,
IOptionsMonitor<ServiceVerifyConfigOption> _verifyConfig) : IAsyncAuthorizationFilter
{
public IStringLocalizer _localizer { get; set; }
private readonly IFusionCache _fusionCache;
private readonly IUserInfo _userInfo;
private readonly IOptionsMonitor<ServiceVerifyConfigOption> _verifyConfig;
public LimitUserRequestAuthorization(IFusionCache fusionCache, IUserInfo userInfo, IStringLocalizer localizer, IOptionsMonitor<ServiceVerifyConfigOption> verifyConfig)
{
_fusionCache = fusionCache;
_userInfo = userInfo;
_verifyConfig = verifyConfig;
_localizer = localizer;
}
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{

View File

@ -0,0 +1,85 @@
using IRaCIS.Core.Application.Helper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace IRaCIS.Core.Application.Service.BusinessFilter;
#region minimalapi 流程
public class LimitUserRequestAuthorizationEndpointFilter(
IFusionCache _fusionCache, IUserInfo _userInfo, IStringLocalizer _localizer,
IOptionsMonitor<ServiceVerifyConfigOption> _verifyConfig) : IEndpointFilter
{
public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
{
var minutes = _verifyConfig.CurrentValue.AutoLoginOutMinutes;
if (_verifyConfig.CurrentValue.OpenLoginLimit)
{
// 如果此请求允许匿名访问,不进行处理
var endpoint = context.HttpContext.GetEndpoint();
if (endpoint?.Metadata?.GetMetadata<IAllowAnonymous>() != null)
{
return await next(context);
}
// 没有从请求中取到 token
if (string.IsNullOrWhiteSpace(_userInfo.UserToken))
{
context.HttpContext.Response.ContentType = "application/json";
return Results.Json(ResponseOutput.NotOk(_localizer["LimitUser_AuthTokenMissing"]), statusCode: StatusCodes.Status200OK);
}
// 获取缓存中的用户 token
var cacheUserToken = await _fusionCache.GetOrDefaultAsync<string>(CacheKeys.UserToken(_userInfo.Id));
// 缓存中没有取到 token
if (string.IsNullOrWhiteSpace(cacheUserToken))
{
// 设置当前用户最新 token
await _fusionCache.SetAsync(CacheKeys.UserToken(_userInfo.Id), _userInfo.UserToken, TimeSpan.FromDays(7));
await _fusionCache.SetAsync(CacheKeys.UserAutoLoginOut(_userInfo.Id), DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), TimeSpan.FromMinutes(minutes));
}
// 如果是同一个用户
else if (cacheUserToken == _userInfo.UserToken)
{
var cacheTime = await _fusionCache.GetOrDefaultAsync<string>(CacheKeys.UserAutoLoginOut(_userInfo.Id));
// 如果过期,自动登出
if (string.IsNullOrEmpty(cacheTime))
{
context.HttpContext.Response.ContentType = "application/json";
return Results.Json(ResponseOutput.NotOk(_localizer["LimitUser_AccountAuto_LoginOut"], ApiResponseCodeEnum.AutoLoginOut), statusCode: StatusCodes.Status403Forbidden);
}
else
{
await _fusionCache.SetAsync(CacheKeys.UserAutoLoginOut(_userInfo.Id), DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), TimeSpan.FromMinutes(minutes));
}
}
else
{
// 如果账户在其他地方已登录,当前用户被迫下线
context.HttpContext.Response.ContentType = "application/json";
return Results.Json(ResponseOutput.NotOk(_localizer["LimitUser_AccountLoggedInElsewhere"], ApiResponseCodeEnum.LoginInOtherPlace), statusCode: StatusCodes.Status403Forbidden);
}
}
// 如果通过授权,继续执行请求管道
return await next(context);
}
}
#endregion

View File

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
@ -11,14 +12,8 @@ namespace IRaCIS.Core.Application.Service.BusinessFilter;
#region minimalapi 流程
public class UnifiedApiResultEndpointFilter : IEndpointFilter
public class UnifiedApiResultEndpointFilter(ILogger<UnifiedApiResultEndpointFilter> _logger) : IEndpointFilter
{
private readonly ILogger _logger;
public UnifiedApiResultEndpointFilter(ILogger<UnifiedApiResultFilter> logger)
{
_logger = logger;
}
public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
{
@ -37,8 +32,13 @@ public class UnifiedApiResultEndpointFilter : IEndpointFilter
return ResponseOutput.Ok(tuple.Item1, tuple.Item2);
}
if (result is IResponseOutput)
{
if (result is IResponseOutput responseOutput)
{
if (!string.IsNullOrWhiteSpace(responseOutput.LocalizedInfo))
{
//统一在这里记录国际化的日志信息
_logger.LogWarning($"{responseOutput.LocalizedInfo}");
}
return result;
}

View File

@ -58,7 +58,7 @@ namespace IRaCIS.Core.Application.Service
public IResponseOutput GetTest()
{
throw new BusinessValidationFailedException("手动抛出的异常");
//throw new BusinessValidationFailedException("手动抛出的异常");
return ResponseOutput.Ok(_userInfo.IP);
}