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 类

  • 继承自 AttributeIFilterFactory
  • 可应用于控制器类或方法上 (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 响应自适应
✅ 详细的日志记录

这是一个企业级应用中常见的统一认证授权解决方案。

评论加载中...