• .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) 。

  • 相关阅读:
    Linux 添加环境变量
    postgresql 获取修改列的值
    5月30日周一上午
    周日5月29日
    2016年5月26日
    如何使用Gson(添加到项目里去)
    linux内核分析课程总结()待完善
    5月5日离散课笔记
    4月28日的离散课(还少了一部分)
    2016年4月29日
  • 原文地址:https://www.cnblogs.com/MingsonZheng/p/15920886.html
Copyright © 2020-2023  润新知