权限系统设计

This commit is contained in:
hello 2024-04-03 16:37:59 +08:00
parent 67c567eb2c
commit afd2c68fbf
4 changed files with 217 additions and 0 deletions

View File

@ -0,0 +1,7 @@
## 模型绑定约定
实体对象是 EF 中的概念, 每个实体对象对应数据库中的一张表。模型对象是 MVC 中的概念,是 HTTP 请求和响应的数据结构。HTTP 请求中通过 URL 参数、表单、标头、 JSON 数据等方式传递数据,这些数据最终会被绑定为模型对象中,模型经过转换为实体对象后,被持久化到数据库中。
针对不同 HTTP 操作,应该使用不同的模型对象,比如创建模型、更新模型、查询模型、删除模型等,这样的好处是可以更好的区分模型对象的职责,比如创建模型只需要包含创建所需的字段,更新模型只需要包含更新所需的字段,查询模型只需要包含查询所需的字段,删除模型只需要包含删除所需的字段,职责单一,维护性好,还可以针对不同的模型提供不同的验证规则。
![model-entity-mapper](https://oss.xcode.me/notes/helloshop/model-entity-mapper.svg)

View File

@ -0,0 +1,66 @@
# 自动映射实体和模型
## 使用 AutoMapper 自动映射实体和模型
AutoMapper 是一个对象映射工具,可以自动映射实体对象和模型对象,减少手动映射的工作量,提高开发效率,除此之外 Mapster 也是一个高性能的对象映射工具支持源生成代码性能更好。AutoMapper 比 Mapster 更加流行更加成熟更加稳定更加易用更加灵活更加强大更加全面更加受欢迎。AutoMapper 是 .NET Foundation 的一部分,是一个开源项目,是一个非常优秀的对象映射工具。
## 安装 NuGet 包
```shell
dotnet add package AutoMapper
```
## 在依赖注入中注册 AutoMapper
```csharp
builder.Services.AddAutoMapper(Assembly.GetExecutingAssembly());
```
## 创建映射配置文件
```csharp
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<User, UserDto>();
CreateMap<UserDto, User>();
}
}
```
## 使用 AutoMapper 映射实体和模型
```csharp
public class UserService
{
private readonly IMapper _mapper;
public UserService(IMapper mapper)
{
_mapper = mapper;
}
public UserDto GetUser(int id)
{
var user = _dbContext.Users.Find(id);
return _mapper.Map<UserDto>(user);
}
public void UpdateUser(UserDto userDto)
{
var user = _mapper.Map<User>(userDto);
_dbContext.Users.Update(user);
_dbContext.SaveChanges();
}
}
```
## 分页模式
常见的分页模式有两种,一种是基于页码和页大小的分页模式,一种是基于游标和页大小的分页模式。

View File

@ -0,0 +1,54 @@
# 模型自动验证机制
## 使用 FluentValidation 开源库
FluentValidation 是一个.NET库用于构建类型安全的验证规则。它的设计目标是提供一个简单、清晰的API同时还能够支持复杂的验证规则。
```shell
dotnet add package FluentValidation.DependencyInjectionExtensions
```
## 实现验证器
```csharp
public class UserValidator : AbstractValidator<User>
{
public UserValidator()
{
RuleFor(x => x.Name).NotEmpty().MaximumLength(32);
RuleFor(x => x.Email).NotEmpty().EmailAddress();
}
}
```
## 自动依赖注入
```csharp
builder.Services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
```
## 自定义验证错误消息
```csharp
public class UserValidator : AbstractValidator<User>
{
public UserValidator()
{
RuleFor(x => x.Name).NotEmpty().MaximumLength(32).WithMessage("Name is required and must be less than 32 characters.");
RuleFor(x => x.Email).NotEmpty().EmailAddress().WithMessage("Email is required and must be a valid email address.");
}
}
```
## 通用错误消息
```csharp
public class UserValidator : AbstractValidator<User>
{
public UserValidator()
{
RuleFor(x => x.Name).NotEmpty().MaximumLength(32).WithMessage("{PropertyName} is required and must be less than {MaxLength} characters.");
RuleFor(x => x.Email).NotEmpty().EmailAddress().WithMessage("{PropertyName} is required and must be a valid email address.");
}
}
```

View File

@ -0,0 +1,90 @@
# 基于资源的授权的最佳实践
## 在 Identity Service 中提供权限检查接口
```csharp
[HttpHead]
public async Task<ActionResult<IEnumerable<PermissionGrantedResponse>>> CheckPermission(string permissionName, string? resourceType = null, string? resourceId = null
{
if (await permissionChecker.IsGrantedAsync(permissionName, resourceType, resourceId))
{
return Ok();
}
return Forbid();
}
```
## 远程权限检查器
```csharp
Dictionary<string, string?> parameters = new()
{
[nameof(name)] = name,
[nameof(resourceType)] = resourceType,
[nameof(resourceId)] = resourceId
};
string queryString = QueryHelpers.AddQueryString(string.Empty, parameters);
HttpRequestMessage request = new(HttpMethod.Head, queryString);
using HttpResponseMessage response = await httpClient.SendAsync(request);
return response.IsSuccessStatusCode;
```
## 重新实现权限处理程序
```csharp
public class PermissionRequirementHandler(IPermissionChecker permissionChecker) : AuthorizationHandler<OperationAuthorizationRequirement>
{
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, OperationAuthorizationRequirement requirement)
{
if (context.Resource is IAuthorizationResource resource)
{
if (await permissionChecker.IsGrantedAsync(context.User, requirement.Name, resource.ResourceType, resource.ResourceId))
{
context.Succeed(requirement);
}
else
{
context.Fail();
}
return;
}
if (await permissionChecker.IsGrantedAsync(context.User, requirement.Name))
{
context.Succeed(requirement);
return;
}
context.Fail();
}
}
```
## 取消缓存以便测试
```csharp
await distributedCache.SetObjectAsync(cacheKey, new PermissionGrantCacheItem(isGranted), new DistributedCacheEntryOptions
{
AbsoluteExpiration = DateTimeOffset.Now
});
```
## 可读性重构
重新生成演示数据,并将授权中的 Name 改为 PermissionName具有更强的可读性。
## 资源描述重构
```csharp
public record struct ResourceInfo(string ResourceType, string ResourceId) : IAuthorizationResource
{
public override readonly string ToString() => $"{ResourceType}:{ResourceId}";
}
```