基于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
相关产品推荐
相关产品推荐

