.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 ... 
随机推荐
- [转]浮点运算decimal.js
			开发过程中免不了有浮点运算,JavaScript浮点运算的精度问题会带来一些困扰 JavaScript 只有一种数字类型 ( Number ) JavaScript采用 IEEE 754 标准双精度浮 ... 
- java集合对比汇总
			List.Set和Map: List是有序的集合,Set是无序的集合.Map是无序的键值对. HashMap详解: HashMap有两个参数影响其性能:初始容量和加载因子.默认初始容量是16,加载因子 ... 
- 【Java常用类】SimpleDateFormat
			文章目录 SimpleDateFormat 默认构造器实例化对象 默认构造器的格式化 带参构造器实例化对象 带参构造器的格式化 自定义格式 解析 SimpleDateFormat 默认构造器实例化对象 ... 
- 《剑指offer》面试题42. 连续子数组的最大和
			问题描述 输入一个整型数组,数组里有正数也有负数.数组中的一个或连续多个整数组成一个子数组.求所有子数组的和的最大值. 要求时间复杂度为O(n). 示例1: 输入: nums = [-2,1,-3,4 ... 
- GIL全局解释器锁、死锁现象、python多线程的用处、进程池与线程池理论
			昨日内容回顾 僵尸进程与孤儿进程 # 僵尸进程: 所有的进程在运行结束之后并不会立刻销毁(父进程需要获取该进程的资源) # 孤儿进程: 子进程正常运行 但是产生该子进程的父进程意外死亡 # 守护进程: ... 
- centos6.6手动安装mysql5.5并配置主从同步
			0.实验环境 主机IP(Master) 192.168.61.150 centos6.6 从机IP(Slave) 192.168.61.157 centos6.6 1.查看centos系统版本 [ ... 
- ajax的核心
			<script> // ajax 简称(a 代表异步 j 代表javascript a 代表 and x 代表xml--是一种带有标签的数据格式,被json取代了) //ajax 是异步对 ... 
- 利用SelectPdf插件将网页生成PDF
			简介 适用于.NET Framework和.NET Core的HTML至PDF转换器 SelectPdf提供的在线html到pdf转换器使用.NET的Select.Pdf库中的html到pdf转换器. ... 
- 🏆【Alibaba中间件技术系列】「Nacos技术专题」服务注册与发现相关的原理分析
			背景介绍 前几篇文章介绍了Nacos配置中心服务的能力机制,接下来,我们来介绍Nacos另一个非常重要的特性就是服务注册与发现,说到服务的注册与发现相信大家应该都不陌生,在微服务盛行的今天,服务是非常 ... 
- golang中结构体中的嵌套
			package main import "fmt" type Base struct { name string } func (b *Base) m1() int { retur ... 
