.NET 云原生架构师训练营(权限系统 代码重构)--学习笔记
目录
- 模块拆分
- 代码重构
模块拆分

代码重构
- AuthenticationController
- PermissionController
- IAuthorizationMiddlewareResultHandler
- ISaveChangesInterceptor
AuthenticationController
新增 AuthenticationController 用于登录和注册;登录会颁发 jwt token,包含用户的 claims 和 role 的 claims
登录
[HttpPost]
[Route("login")]
public async Task<IActionResult> Login([FromBody] LoginRequest.LoginModel model)
{
var user = await _userManager.FindByNameAsync(model.Username);
var userClaims = await _userManager.GetClaimsAsync(user);
if (user != null && await _userManager.CheckPasswordAsync(user, model.Password))
{
var userRoles = await _userManager.GetRolesAsync(user);
var authClaims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.UserName),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
};
foreach (var userRole in userRoles)
{
authClaims.Add(new Claim(ClaimTypes.Role, userRole));
var role = await _roleManager.FindByNameAsync(userRole);
var roleClaims = await _roleManager.GetClaimsAsync(role);
authClaims.AddRange(roleClaims);
}
authClaims.AddRange(userClaims);
var authSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JWT:Secret"]));
var token = new JwtSecurityToken(
issuer: _configuration["JWT:ValidIssuer"],
audience: _configuration["JWT:ValidAudience"],
expires: DateTime.Now.AddHours(3),
claims: authClaims,
signingCredentials: new SigningCredentials(authSigningKey, SecurityAlgorithms.HmacSha256)
);
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token),
expiration = token.ValidTo
});
}
return Unauthorized();
}
注册
[HttpPost]
[Route("register")]
public async Task<IActionResult> Register([FromBody] RegisterRequest model)
{
var userExists = await _userManager.FindByNameAsync(model.Username);
if (userExists != null)
return StatusCode(StatusCodes.Status500InternalServerError, "User already exist");
var user = new IdentityUser()
{
Email = model.Email,
SecurityStamp = Guid.NewGuid().ToString(),
UserName = model.Username
};
var result = await _userManager.CreateAsync(user, model.Password);
if (!result.Succeeded)
return StatusCode(StatusCodes.Status500InternalServerError, result.Errors);
return Ok();
}
PermissionController
PermissionController 新增 创建实体权限,用户角色相关接口
[Route("entity")]
[HttpPost]
public async Task<IActionResult> CreateEntityPermission([FromBody] CreateEntityPermissionRequest request)
{
var permission = new Permission()
{
Data = request.Data,
Description = request.Description,
DisplayName = request.DisplayName,
Key = request.Key,
Group = request.Group,
Resources = request.resources.Select(r => new EntityResource() { Key = r })
};
await _permissionManager.CreateAsync(permission);
return Ok();
}
[Route("user/{username}")]
[HttpGet]
public async Task<IActionResult> FindUserPermission(string username)
{
return Ok(await _userPermission.FindUserPermission(username));
}
[Route("role/{roleName}")]
[HttpGet]
public async Task<IActionResult> FindRolePermission(string roleName)
{
return Ok(await _rolePermission.FindRolePermission(roleName));
}
[Route("addtorole")]
[HttpPost]
public async Task<IActionResult> AddToRole([FromQuery] string role, [FromQuery] string permission)
{
await _rolePermission.AddRolePermission(role, permission);
return Ok();
}
[Route("addtouser")]
[HttpPost]
public async Task<IActionResult> AddToUser([FromQuery] string username, [FromQuery] string permission)
{
await _userPermission.AddUserPermission(username, permission);
return Ok();
}
IAuthorizationMiddlewareResultHandler
在 asp .net core 3.1 之后 ActionAuthorizationFilter 已经没用了,需要使用 IAuthorizationMiddlewareResultHandler
IAuthorizationMiddlewareResultHandler 是授权管理里面的一个 Handler,可以从 endpoint 的 Metadata 获取到 ControllerActionDescriptor,从而获取到 permissionKey
authorizeResult.Challenged:未登录返回401
authorizeResult.Forbidden:未授权返回403
需要将 Challenged 放在 Forbidden 之前,不然未登录也返回 403
public class DotNetNBAuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler
{
public async Task HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy,
PolicyAuthorizationResult authorizeResult)
{
var endpoint = context.GetEndpoint();
var actionDescriptor = endpoint.Metadata.GetMetadata<ControllerActionDescriptor>();
if (actionDescriptor != null)
{
var permissions = context.User.Claims.Where(c => c.Type == Core.ClaimsTypes.Permission);
var permissionKey = actionDescriptor.GetPermissionKey();
var values = permissions.Select(p => p.Value);
if (!values.Contains(permissionKey))
{
await context.ForbidAsync();
return;
}
}
if (authorizeResult.Challenged)
{
if (policy.AuthenticationSchemes.Count > 0)
{
foreach (var scheme in policy.AuthenticationSchemes)
{
await context.ChallengeAsync(scheme);
}
}
else
{
await context.ChallengeAsync();
}
return;
}
else if (authorizeResult.Forbidden)
{
if (policy.AuthenticationSchemes.Count > 0)
{
foreach (var scheme in policy.AuthenticationSchemes)
{
await context.ForbidAsync(scheme);
}
}
else
{
await context.ForbidAsync();
}
return;
}
await next(context);
}
}
ISaveChangesInterceptor
ISaveChangesInterceptor 是 entity 操作的拦截器,获取 Added,Deleted,Modified 三种状态的实体
新增和删除分别获取对应的 permission key 与用户的 permission 对比
更新需要遍历获取每个实体更新的 member 的 permission key 与用户的 permission 对比
public class SavingChangeInterceptor: ISaveChangesInterceptor
{
...
public async ValueTask<InterceptionResult<int>> SavingChangesAsync(DbContextEventData eventData, InterceptionResult<int> result,
CancellationToken cancellationToken = new CancellationToken())
{
var contextName = eventData.Context.GetType().Name;
var permissions = await _permissionManager.GetByGroupAsync(contextName);
var entityPermissions = permissions.Select(p => new EntityPermission(p)).ToList();
if (permissions==null || !permissions.Any())
return result;
var addedEntities = eventData.Context.ChangeTracker.Entries().Where(e => e.State == EntityState.Added);
var deletedEntties = eventData.Context.ChangeTracker.Entries().Where(e => e.State == EntityState.Deleted);
var modifiedEntities = eventData.Context.ChangeTracker.Entries().Where(e => e.State == EntityState.Modified);
await CheckAddedEntitiesAsync(addedEntities, entityPermissions);
await CheckDeletedEntitiesAsync(deletedEntties,entityPermissions);
await CheckModifiedEntitiesAsync(modifiedEntities,entityPermissions);
return result;
}
private async Task CheckAddedEntitiesAsync(IEnumerable<EntityEntry> entities,IEnumerable<EntityPermission> permissions)
{
foreach (var entity in entities)
{
var entityName = entity.Metadata.Name;
var entityPermissions = permissions.Where(p => p.Data.EntityName == entityName);
if(!entityPermissions.Any())
continue;
var user = _contextAccessor.HttpContext.User;
if (!user.Identity.IsAuthenticated)
throw new AuthenticationException();
var createPermission = entityPermissions.Where(e => e.Data.Create).Select(e => e.Key);
var claimValues = user.Claims.Where(c => c.Type == ClaimsTypes.Permission).Select(c => c.Value);
if (!createPermission.Intersect(claimValues).Any())
throw new AuthorizationException();
}
}
private async Task CheckDeletedEntitiesAsync(IEnumerable<EntityEntry> entities,IEnumerable<EntityPermission> permissions)
{
foreach (var entity in entities)
{
var entityName = entity.Metadata.Name;
var entityPermissions = permissions.Where(p => p.Data.EntityName == entityName);
if(!entityPermissions.Any())
continue;
var user = _contextAccessor.HttpContext.User;
if (!user.Identity.IsAuthenticated)
throw new AuthenticationException();
var deletePermission = entityPermissions.Where(e => e.Data.Delete).Select(e => e.Key);
var claimValues = user.Claims.Where(c => c.Type == ClaimsTypes.Permission).Select(c => c.Value);
if (!deletePermission.Intersect(claimValues).Any())
throw new AuthorizationException();
}
}
private async Task CheckModifiedEntitiesAsync(IEnumerable<EntityEntry> entities,IEnumerable<EntityPermission> permissions)
{
foreach (var entity in entities)
{
var entityName = entity.Metadata.Name;
var entityPermissions = permissions.Where(p => p.Data.EntityName == entityName);
if(!entityPermissions.Any())
continue;
var user = _contextAccessor.HttpContext.User;
if (!user.Identity.IsAuthenticated)
throw new AuthenticationException();
var modifiedMembers = entity.Members.Where(m => m.IsModified).Select(m => m.Metadata.Name);
var canUpdatePermissionKeys = new List<string>();
foreach (var permission in entityPermissions)
{
var definedMembers = permission.Data.Members.Where(m => m.Update && modifiedMembers.Contains(m.MemberName));
if(definedMembers.Any())
canUpdatePermissionKeys.Add((permission.Key));
}
var claimValues = user.Claims.Where(c => c.Type == ClaimsTypes.Permission).Select(c => c.Value);
if (!canUpdatePermissionKeys.Intersect(claimValues).Any())
throw new AuthorizationException();
}
}
...
}
GitHub源码链接:
https://github.com/MingsonZheng/dotnetnb.security refactor 分支
课程链接
https://appsqsyiqlk5791.h5.xiaoeknow.com/v1/course/video/v_5f39bdb8e4b01187873136cf?type=2

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
欢迎转载、使用、重新发布,但务必保留文章署名 郑子铭 (包含链接: http://www.cnblogs.com/MingsonZheng/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。
如有任何疑问,请与我联系 (MingsonZheng@outlook.com) 。
.NET 云原生架构师训练营(权限系统 代码重构)--学习笔记的更多相关文章
- .NET 云原生架构师训练营(Identity Server)--学习笔记
目录 OAuth 2.0 OpenID Connect QuickStart OAuth 2.0 概念 过程 通信 组件 示例代码 概念 OAuth 2.0 是一个授权协议,它允许软件应用代表(而不是 ...
- .NET 云原生架构师训练营(组合模式)--学习笔记
目录 引入 组合模式 源码 引入 在上一篇执行 _connectionDelegate 之后,HttpConnectionMiddleware 处理请求 return connection.Proce ...
- .NET 云原生架构师训练营(模板方法 && 建造者)--学习笔记
目录 模板方法 源码 建造者 模板方法 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤 源码 https://github.com ...
- .NET 云原生架构师训练营(系统架构)--学习笔记
目录 对外展现的功能 内部功能 功能交互与价值通路 系统架构 目标 认识系统的价值通路 认识功能架构,通过把功能结构与形式结构结合来描述系统架构 受益原则 好的架构必须使人受益,要想把架构做好,就要专 ...
- .NET 云原生架构师训练营(模块一 架构师与云原生)--学习笔记
目录 什么是软件架构 软件架构的基本思路 单体向分布式演进.云原生.技术中台 1.1 什么是软件架构 1.1.1 什么是架构? Software architecture = {Elements, F ...
- .NET 云原生架构师训练营(权限系统 RGCA 架构设计)--学习笔记
目录 项目核心内容 实战目标 RGCA 四步架构法 项目核心内容 无代码埋点实现对所有 API Action 访问控制管理 对 EF Core 实体新增.删除.字段级读写控制管理 与 Identity ...
- .NET 云原生架构师训练营(建立系统观)--学习笔记
目录 目标 ASP .NET Core 什么是系统 什么是系统思维 系统分解 什么是复杂系统 作业 目标 通过整体定义去认识系统 通过分解去简化对系统的认识 ASP .NET Core ASP .NE ...
- .NET 云原生架构师训练营(权限系统 RGCA 开发任务)--学习笔记
目录 目标 模块拆分 OPM 开发任务 目标 基于上一讲的模块划分做一个任务拆解,根据任务拆解实现功能 模块拆分 模块划分已经完成了边界的划分,边界内外职责清晰 OPM 根据模块拆分画出 OPM(Ob ...
- .NET 云原生架构师训练营(权限系统 代码实现 ActionAccess)--学习笔记
目录 开发任务 代码实现 开发任务 DotNetNB.Security.Core:定义 core,models,Istore:实现 default memory store DotNetNB.Secu ...
随机推荐
- 性能优化-使用双buffer实现无锁队列
借助本文,实现一种在"读多写一"场景下的无锁实现方式 在我们的工作中,多线程编程是一件太稀松平常的事.在多线程环境下操作一个变量或者一块缓存,如果不对其操作加以限制,轻则变量值或者 ...
- 《剑指offer》面试题03. 数组中重复的数字
问题描述 找出数组中重复的数字. 在一个长度为 n 的数组 nums 里的所有数字都在 0-n-1 的范围内.数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次.请找出数组中 ...
- leetcode 33. 搜索旋转排序数组 及 81. 搜索旋转排序数组 II
33. 搜索旋转排序数组 问题描述 假设按照升序排序的数组在预先未知的某个点上进行了旋转. ( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] ). 搜索一个给定 ...
- leetcode 1218. 最长定差子序列
问题描述 给你一个整数数组 arr 和一个整数 difference,请你找出 arr 中所有相邻元素之间的差等于给定 difference 的等差子序列,并返回其中最长的等差子序列的长度. 示例 ...
- Nginx 反向代理解决跨域问题分析
当你遇到跨域问题,不要立刻就选择复制去尝试.请详细看完这篇文章再处理 .我相信它能帮到你. 分析前准备: 前端网站地址:http://localhost:8080 服务端网址:http://local ...
- Android 12(S) 图形显示系统 - 示例应用(二)
1 前言 为了更深刻的理解Android图形系统抽象的概念和BufferQueue的工作机制,这篇文章我们将从Native Level入手,基于Android图形系统API写作一个简单的图形处理小程序 ...
- 【刷题-LeetCode】203. Remove Linked List Elements
Remove Linked List Elements Remove all elements from a linked list of integers that have value *val* ...
- 【解决了一个小问题】vmselect对应的vmstorage端口配置错误导致的问题
从vmselect查询的时候,出现如下错误: error when executing query="up" on the time range (start=1639388706 ...
- 三张图秒懂Redis集群设计原理
转载Redis Cluster原理 转载https://blog.csdn.net/yejingtao703/article/details/78484151 redis集群部署方式: 单机 主从 r ...
- 针对vue中请求数据对象新添加的属性不能响应式的解决方法
1.需要给对象添加属性时,不能采用传统的obj.属性=值,obj[属性]=值 来添加属性,在vue页面时需要这样使用 this.$set(obj,"propertyName",&q ...