minimal api 踢用户下线 实现
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
parent
947d6dc6d1
commit
74a0c8923c
|
@ -149,6 +149,7 @@ builder.Services.AddMasaMinimalAPIs(options =>
|
||||||
|
|
||||||
options.RouteHandlerBuilder= t=> {
|
options.RouteHandlerBuilder= t=> {
|
||||||
t.RequireAuthorization()
|
t.RequireAuthorization()
|
||||||
|
.AddEndpointFilter<LimitUserRequestAuthorizationEndpointFilter>()
|
||||||
.AddEndpointFilter<UnifiedApiResultEndpointFilter>()
|
.AddEndpointFilter<UnifiedApiResultEndpointFilter>()
|
||||||
.WithGroupName("Institution");
|
.WithGroupName("Institution");
|
||||||
};
|
};
|
||||||
|
@ -191,27 +192,6 @@ app.UseStatusCodePages(async context =>
|
||||||
//app.UseExceptionHandler();
|
//app.UseExceptionHandler();
|
||||||
app.UseExceptionHandler(o => { });
|
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 暂时废弃
|
#region 暂时废弃
|
||||||
|
|
||||||
//app.UseMiddleware<MultiDiskStaticFilesMiddleware>();
|
//app.UseMiddleware<MultiDiskStaticFilesMiddleware>();
|
||||||
|
|
|
@ -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)
|
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
|
||||||
{
|
{
|
|
@ -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
|
|
@ -1,4 +1,5 @@
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -11,14 +12,8 @@ namespace IRaCIS.Core.Application.Service.BusinessFilter;
|
||||||
#region minimalapi 流程
|
#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)
|
public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
|
||||||
{
|
{
|
||||||
|
@ -37,8 +32,13 @@ public class UnifiedApiResultEndpointFilter : IEndpointFilter
|
||||||
return ResponseOutput.Ok(tuple.Item1, tuple.Item2);
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ namespace IRaCIS.Core.Application.Service
|
||||||
|
|
||||||
public IResponseOutput GetTest()
|
public IResponseOutput GetTest()
|
||||||
{
|
{
|
||||||
throw new BusinessValidationFailedException("手动抛出的异常");
|
//throw new BusinessValidationFailedException("手动抛出的异常");
|
||||||
|
|
||||||
return ResponseOutput.Ok(_userInfo.IP);
|
return ResponseOutput.Ok(_userInfo.IP);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue