You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于C#的整洁架构中如何实现认证与授权?

解决方案

1. 定义授权标记(可选但推荐)

通过特性或接口标记需要授权的请求,实现精细化控制:

方式一:特性标记

[AttributeUsage(AttributeTargets.Class)]
public class AuthorizeRequestAttribute : Attribute
{
    public string[] RequiredPermissions { get; }

    public AuthorizeRequestAttribute(params string[] requiredPermissions)
    {
        RequiredPermissions = requiredPermissions;
    }
}

方式二:接口标记

public interface IAuthorizableRequest
{
    string RequiredPermission { get; }
}

2. 实现认证授权装饰器

采用装饰器模式包装IRequestHandler,在执行业务逻辑前拦截并处理授权:

public class AuthorizedRequestHandlerDecorator<TRequest, TResult> : IRequestHandler<TRequest, TResult>
    where TRequest : IRequest<TResult>
{
    private readonly IRequestHandler<TRequest, TResult> _innerHandler;
    private readonly IAuthorizationService _authorizationService;
    private readonly IHttpContextAccessor _httpContextAccessor;

    public AuthorizedRequestHandlerDecorator(
        IRequestHandler<TRequest, TResult> innerHandler,
        IAuthorizationService authorizationService,
        IHttpContextAccessor httpContextAccessor)
    {
        _innerHandler = innerHandler;
        _authorizationService = authorizationService;
        _httpContextAccessor = httpContextAccessor;
    }

    public async Task<TResult> HandleAsync(TRequest request, CancellationToken cancellationToken)
    {
        // 判断当前请求是否需要授权
        var requiresAuth = typeof(TRequest).GetCustomAttribute<AuthorizeRequestAttribute>() != null 
                          || typeof(TRequest).IsAssignableTo(typeof(IAuthorizableRequest));

        if (!requiresAuth)
        {
            return await _innerHandler.HandleAsync(request, cancellationToken);
        }

        // 检查用户是否已认证
        var user = _httpContextAccessor.HttpContext?.User;
        if (user == null || !user.Identity.IsAuthenticated)
        {
            return CreateFailureResult("用户未登录");
        }

        // 执行权限校验(以特性为例)
        var authAttr = typeof(TRequest).GetCustomAttribute<AuthorizeRequestAttribute>();
        if (authAttr?.RequiredPermissions != null && authAttr.RequiredPermissions.Any())
        {
            foreach (var perm in authAttr.RequiredPermissions)
            {
                var authResult = await _authorizationService.AuthorizeAsync(user, perm);
                if (!authResult.Succeeded)
                {
                    return CreateFailureResult($"无权限执行该操作:{perm}");
                }
            }
        }

        // 授权通过,执行原业务逻辑
        return await _innerHandler.HandleAsync(request, cancellationToken);
    }

    // 构造兼容TResult的失败结果,需根据实际结果类型调整实现
    private TResult CreateFailureResult(string message)
    {
        // 示例:若TResult为通用ApiResult<T>类型
        if (typeof(TResult).IsGenericType && typeof(TResult).GetGenericTypeDefinition() == typeof(ApiResult<>))
        {
            var genericType = typeof(TResult).GetGenericArguments()[0];
            var resultType = typeof(ApiResult<>).MakeGenericType(genericType);
            return (TResult)Activator.CreateInstance(resultType, false, message, default);
        }

        // 针对自定义结果类型,通过反射设置失败状态
        var result = Activator.CreateInstance<TResult>();
        typeof(TResult).GetProperty("Success")?.SetValue(result, false);
        typeof(TResult).GetProperty("ErrorMessage")?.SetValue(result, message);
        return result;
    }
}

3. 配置DI容器注册装饰器

使用Scrutor库简化装饰器注册(需安装Scrutor NuGet包):

// 注册所有IRequestHandler实现
builder.Services.Scan(scan => scan
    .FromAssembliesOf(typeof(IRequestHandler<,>))
    .AddClasses(classes => classes.AssignableTo(typeof(IRequestHandler<,>)))
    .AsImplementedInterfaces()
    .WithTransientLifetime());

// 注册装饰器,替换原Handler实例
builder.Services.Decorate(typeof(IRequestHandler<,>), typeof(AuthorizedRequestHandlerDecorator<,>));

若不想依赖第三方库,可手动注册每个Handler的装饰器实例,但Scrutor能大幅简化批量注册逻辑。

4. 统一结果类型(关键)

为了简化失败结果的构造,建议定义通用结果类型:

public class ApiResult<T>
{
    public bool Success { get; set; }
    public string ErrorMessage { get; set; }
    public T Data { get; set; }

    public ApiResult(bool success, string errorMessage, T data)
    {
        Success = success;
        ErrorMessage = errorMessage;
        Data = data;
    }

    // 静态工厂方法快速创建结果
    public static ApiResult<T> Ok(T data) => new(true, null, data);
    public static ApiResult<T> Fail(string message) => new(false, message, default);
}

后续所有IRequestHandler的TResult都使用ApiResult<T>,CreateFailureResult可直接返回ApiResult<T>.Fail(message),无需反射。

5. 使用示例

标记需要授权的请求并实现Handler:

[AuthorizeRequest("DeleteUser")]
public class DeleteUserRequest : IRequest<ApiResult<bool>>
{
    public int UserId { get; set; }
}

public class DeleteUserHandler : IRequestHandler<DeleteUserRequest, ApiResult<bool>>
{
    public async Task<ApiResult<bool>> HandleAsync(DeleteUserRequest request, CancellationToken cancellationToken)
    {
        // 执行业务逻辑:删除用户
        return ApiResult<bool>.Ok(true);
    }
}

当调用该Handler时,装饰器会先完成认证授权校验,失败则直接返回失败结果,不会执行后续业务逻辑。

分层说明

在整洁架构中:

  • 认证属于基础设施层(处理Token解析、用户身份验证等)
  • 授权属于应用层(判断用户是否有权限执行用例)
    本方案将授权逻辑放在应用层的装饰器中,符合分层原则;若需彻底解耦Web层依赖,可将用户身份信息封装到应用层上下文,替代IHttpContextAccessor。

内容的提问来源于stack exchange,提问作者Szyszka947

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 02:02:02