using Dpz.Core.EnumLibrary;
using Dpz.Core.Service.Mediator.Features.Security.Queries;
using Dpz.Core.Web.Library.Middleware;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Filters;
namespace Dpz.Core.Web.Library.Filter;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CheckAuthorizeAttribute : Attribute, IFilterFactory
{
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
return new InternalFilter(
Permissions == 0 ? null : Permissions,
serviceProvider.GetRequiredService<ILoggerFactory>(),
serviceProvider.GetRequiredService<IAccountService>(),
serviceProvider.GetRequiredService<IMediator>(),
serviceProvider.GetRequiredService<IApplicationSignInManager>(),
serviceProvider.GetRequiredService<IAccountSessionService>(),
serviceProvider.GetRequiredService<IHttpCurrentUserService>()
);
}
private class InternalFilter(
Permissions? innerPermissions,
ILoggerFactory logger,
IAccountService accountService,
IMediator mediator,
IApplicationSignInManager signInManager,
IAccountSessionService accountSessionService,
IHttpCurrentUserService currentUserService
) : IAsyncActionFilter
{
private readonly ILogger<InternalFilter> _logger = logger.CreateLogger<InternalFilter>();
private Permissions? InnerPermissions => innerPermissions;
public async Task OnActionExecutionAsync(
ActionExecutingContext context,
ActionExecutionDelegate next
)
{
if (SkipAuthorization(context))
{
await next();
return;
}
var (isValid, errorResult) = ValidateRequest(context);
if (!isValid)
{
context.Result = errorResult!;
return;
}
var userValidationResult = await ValidateUserAsync(context);
if (!userValidationResult.IsValid)
{
context.Result = userValidationResult.ErrorResult!;
return;
}
await next();
}
private static bool SkipAuthorization(ActionContext context)
{
return context.ActionDescriptor.EndpointMetadata.Any(x => x is AllowAnonymousAttribute);
}
private (bool IsValid, IActionResult? ErrorResult) ValidateRequest(ActionContext context)
{
var httpContext = context.HttpContext;
if (!httpContext.User.Authenticated)
{
_logger.LogInformation("Unauthenticated access attempt");
return (
false,
CreateErrorResponse(
context,
httpContext.Request.ExpectsJsonResponse()
? "请先登录"
: "Authentication required",
401
)
);
}
return (true, null);
}
private async Task<(bool IsValid, IActionResult? ErrorResult)> ValidateUserAsync(
ActionExecutingContext context
)
{
var httpContext = context.HttpContext;
var clientUser = await currentUserService.GetUserInfoAsync(httpContext.RequestAborted);
var sessionId = httpContext.User.SessionId;
if (
!string.IsNullOrWhiteSpace(sessionId)
&& await accountSessionService.IsRevokedAsync(sessionId, httpContext.RequestAborted)
)
{
_logger.LogWarning("Revoked session access attempt: {SessionId}", sessionId);
return (
false,
CreateErrorResponse(context, "您的登录会话已被撤销,请重新登录", 302)
);
}
if (clientUser == null)
{
_logger.LogWarning("Invalid user identity");
return (false, CreateErrorResponse(context, "无效的用户身份", 403));
}
var user = await accountService.GetOneUserAsync(clientUser.Id);
if (user == null)
{
_logger.LogWarning("User not found: {Account}", clientUser.Id);
return (false, CreateErrorResponse(context, "用户不存在", 403));
}
var stampResult = await mediator.Send(
new ValidateUserSecurityStampRequest
{
UserId = user.Id,
SecurityStamp = clientUser.Key,
}
);
if (!stampResult.Success || stampResult.Data != UserSecurityValidationResult.Valid)
{
_logger.LogWarning(
"SecurityStamp validation failed for user: {UserName}",
clientUser.Name
);
return (
false,
CreateErrorResponse(context, "您的账户安全状态已更新,请重新登录", 302)
);
}
if (user.Enable == false)
{
_logger.LogWarning("Disabled account access attempt: {UserName}", clientUser.Name);
return (false, CreateErrorResponse(context, "账号已停用", 403));
}
if (!CheckPermissions(user.Permissions))
{
_logger.LogWarning(
"Insufficient permissions for user: {UserName}",
clientUser.Name
);
return (false, CreateErrorResponse(context, "权限不足", 403));
}
if (
clientUser.LastAccessTime.HasValue
&& DateTime.Now - clientUser.LastAccessTime.Value > TimeSpan.FromDays(1)
)
{
_logger.LogInformation(
"User {UserName} has been inactive for more than 1 day",
clientUser.Name
);
await signInManager.RefreshSignInAsync(user);
}
SetViewData(context, user);
return (true, null);
}
private bool CheckPermissions(Permissions? userPermissions)
{
if (InnerPermissions == null)
{
return true;
}
if (userPermissions == null)
{
return false;
}
return (userPermissions.Value & InnerPermissions.Value) == InnerPermissions.Value;
}
private static void SetViewData(ActionExecutingContext context, VmUserInfo user)
{
if (context.Controller is Controller controller)
{
controller.ViewData["CurrentUser"] = user;
}
}
private static IActionResult CreateErrorResponse(
ActionContext context,
string message,
int statusCode
)
{
var request = context.HttpContext.Request;
if (request.ExpectsJsonResponse())
{
return new JsonResult(new ResultInfo(message));
}
return statusCode switch
{
302 => new RedirectToActionResult("ReLogin", "Account", new { }),
401 => new RedirectToActionResult(
"Login",
"Home",
new { fromUrl = request.Path + request.QueryString }
),
403 => CreateForbiddenResult(context, message),
_ => new ContentResult { Content = message, StatusCode = statusCode },
};
}
private static StatusCodeResult CreateForbiddenResult(ActionContext context, string message)
{
context.HttpContext.Items[HtmlErrorPageMiddleware.ErrorMessageItemKey] = message;
return new StatusCodeResult(StatusCodes.Status403Forbidden);
}
}
public Permissions Permissions { get; set; }
public bool IsReusable => false;
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
代码解释
这是一个 ASP.NET Core 的自定义授权过滤器实现,用于在 Action 执行前进行用户身份验证和权限检查。
主要组成部分
1. CheckAuthorizeAttribute 类
- 继承自
Attribute和IFilterFactory - 可应用于控制器类或方法上 (
AttributeUsage) - 包含一个
Permissions属性,用于指定所需权限
2. InternalFilter 内部过滤器
这是实际执行过滤逻辑的类,实现了 IAsyncActionFilter 接口。
核心流程 (OnActionExecutionAsync)
1️⃣ 跳过授权检查
if (SkipAuthorization(context))
- 如果 Action 标记了
[AllowAnonymous],则跳过所有检查
2️⃣ 基础请求验证 (ValidateRequest)
- 检查用户是否已认证 (
httpContext.User.Authenticated) - 未认证返回 401 错误或重定向到登录页
3️⃣ 用户详细验证 (ValidateUserAsync)
执行以下检查:
a. 会话撤销检查
await accountSessionService.IsRevokedAsync(sessionId, ...)
- 检查会话是否被主动撤销
b. 用户存在性检查
var user = await accountService.GetOneUserAsync(clientUser.Id);
c. 安全标记验证
ValidateUserSecurityStampRequest
- 验证用户的 SecurityStamp 是否有效(类似 ASP.NET Identity 的安全戳机制)
- 防止密码修改后旧 Token 仍然有效
d. 账户状态检查
if (user.Enable == false)
- 检查账户是否被禁用
e. 权限检查
CheckPermissions(user.Permissions)
- 使用位运算检查用户是否拥有所需权限
- 逻辑:
(userPermissions & requiredPermissions) == requiredPermissions
f. 会话刷新
if (DateTime.Now - clientUser.LastAccessTime.Value > TimeSpan.FromDays(1))
- 如果用户超过 1 天未活动,刷新登录会话
g. 设置 ViewData
controller.ViewData["CurrentUser"] = user;
- 将当前用户信息传递给视图
错误处理 (CreateErrorResponse)
根据请求类型和状态码返回不同响应:
| 状态码 | JSON 请求 | 非 JSON 请求 |
|---|---|---|
| 401 | 返回 JSON 错误 | 重定向到登录页 |
| 302 | 返回 JSON 错误 | 重定向到重新登录页 |
| 403 | 返回 JSON 错误 | 返回 403 状态页 |
使用示例
[CheckAuthorize(Permissions = Permissions.UserManage)]
public class UserController : Controller
{
// 需要 UserManage 权限才能访问
}
[CheckAuthorize] // 仅需登录
public IActionResult Index() { }
[AllowAnonymous] // 跳过检查
public IActionResult Public() { }
关键特性
✅ 支持权限位运算检查
✅ 会话撤销检测
✅ 安全标记验证
✅ 自动会话刷新
✅ JSON/HTML 响应自适应
✅ 详细的日志记录
这是一个企业级应用中常见的统一认证授权解决方案。
AI 正在分析代码…
评论加载中...