首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >.NET 云原生架构师训练营(权限系统 代码重构)--学习笔记

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

原创
作者头像
郑子铭
修改于 2022-02-24 02:08:56
修改于 2022-02-24 02:08:56
4040
举报

目录

  • 模块拆分
  • 代码重构

模块拆分

image.png
image.png

代码重构

  • AuthenticationController
  • PermissionController
  • IAuthorizationMiddlewareResultHandler
  • ISaveChangesInterceptor

AuthenticationController

新增 AuthenticationController 用于登录和注册;登录会颁发 jwt token,包含用户的 claims 和 role 的 claims

登录

代码语言:txt
AI代码解释
复制
[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();  
}  

注册

代码语言:txt
AI代码解释
复制
[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 新增 创建实体权限,用户角色相关接口

代码语言:txt
AI代码解释
复制
[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

代码语言:txt
AI代码解释
复制
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 对比

代码语言:txt
AI代码解释
复制
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 分支

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
.NET 云原生架构师训练营(权限系统 代码实现 Identity)--学习笔记
添加一个 Identity 的扩展,将 role 和 Permission 结合到一起
郑子铭
2022/02/21
4170
.NET 云原生架构师训练营(权限系统 代码实现 Identity)--学习笔记
.NET 云原生架构师训练营(权限系统 代码实现 Store.EntityFramework)--学习笔记
我们需要在 ResourceProviderHostedService 中读取所有的 Resource,将 Resource 转换为 Permission,再将 Permission 分配给 Role
郑子铭
2022/02/21
2830
.NET 云原生架构师训练营(权限系统 代码实现 Store.EntityFramework)--学习笔记
.NET 云原生架构师训练营(权限系统 代码实现 WebApplication)--学习笔记
创建 ResourceController,通过 ResourceManager 获取所有 Resource
郑子铭
2022/02/21
3650
.NET 云原生架构师训练营(权限系统 代码实现 WebApplication)--学习笔记
.NET 云原生架构师训练营(权限系统 代码实现 ActionAccess)--学习笔记
创建 ActionResource,继承 Resource,包含 ControllerName,ActionName,RouteTemplate 和 HttpVerb 几个属性
郑子铭
2022/02/21
4090
.NET 云原生架构师训练营(权限系统 代码实现 ActionAccess)--学习笔记
.NET 云原生架构师训练营(权限系统 系统演示 EntityAccess)--学习笔记
对 student 的 permission 做一个保护,创建一个 entitiy 的 permission,create 为 true,delete 和 update 为 false
郑子铭
2022/02/23
2960
.NET 云原生架构师训练营(权限系统 系统演示 EntityAccess)--学习笔记
.NET 云原生架构师训练营(权限系统 系统演示 ActionAccess)--学习笔记
完成环境配置之后,将项目 DotNetNB.WebApplication 设置为启动项目,启动之后可以看到 swagger 文档
郑子铭
2022/02/22
3580
.NET 云原生架构师训练营(权限系统 系统演示 ActionAccess)--学习笔记
.NET 云原生架构师训练营(权限系统 RGCA 开发任务)--学习笔记
ActionAccess 模块中的 ActionResourceProvider 会为 RegisterActions 提供支持
郑子铭
2022/03/22
3110
.NET 云原生架构师训练营(权限系统 RGCA 开发任务)--学习笔记
.NET 云原生架构师训练营(权限系统 系统演示 ActionAccess)--学习笔记
完成环境配置之后,将项目 DotNetNB.WebApplication 设置为启动项目,启动之后可以看到 swagger 文档
郑子铭
2022/03/22
2570
.NET 云原生架构师训练营(权限系统 系统演示 ActionAccess)--学习笔记
.NET 云原生架构师训练营(权限系统 RGCA 开发任务)--学习笔记
ActionAccess 模块中的 ActionResourceProvider 会为 RegisterActions 提供支持
郑子铭
2022/02/21
3340
.NET 云原生架构师训练营(权限系统 RGCA 开发任务)--学习笔记
.NET 云原生架构师训练营(权限系统 系统演示 EntityAccess)--学习笔记
对 student 的 permission 做一个保护,创建一个 entitiy 的 permission,create 为 true,delete 和 update 为 false
郑子铭
2022/03/22
2320
.NET 云原生架构师训练营(权限系统 系统演示 EntityAccess)--学习笔记
IdentityServer4实战 - 基于角色的权限控制及Claim详解
一.前言 大家好,许久没有更新博客了,最近从重庆来到了成都,换了个工作环境,前面都比较忙没有什么时间,这次趁着清明假期有时间,又可以分享一些知识给大家。在QQ群里有许多人都问过IdentityServ
晓晨
2018/06/22
2.7K0
ASP.NET Core Authentication and Authorization
最近把一个Asp .net core 2.0的项目迁移到Asp .net core 3.1,项目启动的时候直接报错:
MJ.Zhou
2020/04/08
1.3K0
ASP.NET Core Authentication and Authorization
ASP.NET Core 实战:基于 Jwt Token 的权限控制全揭露
  在涉及到后端项目的开发中,如何实现对于用户权限的管控是需要我们首先考虑的,在实际开发过程中,我们可能会运用一些已经成熟的解决方案帮助我们实现这一功能,而在 Grapefruit.VuCore 这个项目中,我将使用 Jwt 的方式实现对于用户的权限管控,在本章中,我将演示如何使用 Jwt 实现对于用户的授权、鉴权。
程序员宇说
2019/09/11
2.6K0
ASP.NET Core 实战:基于 Jwt Token 的权限控制全揭露
ASP.NET MVC 随想录——探索ASP.NET Identity 身份验证和基于角色的授权,中级篇
在前一篇文章中,我介绍了ASP.NET Identity 基本API的运用并创建了若干用户账号。那么在本篇文章中,我将继续ASP.NET Identity 之旅,向您展示如何运用ASP.NET Identity 进行身份验证(Authentication)以及联合ASP.NET MVC 基于角色的授权(Role-Based Authorization)。 本文的示例,你可以在此下载和预览: 点此进行预览 点此下载示例代码 探索身份验证与授权 在这一小节中,我将阐述和证明ASP.NET 身份验证和
用户1161731
2018/01/11
4.5K0
ASP.NET MVC 随想录——探索ASP.NET Identity 身份验证和基于角色的授权,中级篇
实现一个登录:Mac+.NET 5+Identity+JWT+VS Code
.NET圈儿的朋友们,大家好!我可太喜欢如今开源的.Net了,写代码很巴适!所以今天分享一下之前学习的一个登录小案例,代码有不足之处欢迎指正!!!
郑子铭
2021/11/10
6710
实现一个登录:Mac+.NET 5+Identity+JWT+VS Code
.NET 云原生架构师训练营(权限系统 RGCA 架构设计)--学习笔记
好的架构必须使人受益,要想把架构做好,就要专注于功能的涌现,使得系统把它的主要功能通过跨越系统边界的接口对外展示出来
郑子铭
2022/03/22
6220
.NET 云原生架构师训练营(权限系统 RGCA 架构设计)--学习笔记
.NET 云原生架构师训练营(权限系统 RGCA 架构设计)--学习笔记
好的架构必须使人受益,要想把架构做好,就要专注于功能的涌现,使得系统把它的主要功能通过跨越系统边界的接口对外展示出来
郑子铭
2022/02/21
6290
.NET 云原生架构师训练营(权限系统 RGCA 架构设计)--学习笔记
完美解决asp.net core 3.1 两个AuthenticationScheme(cookie,jwt)共存在一个项目中
在我的项目中有mvc controller(view 和 razor Page)同时也有webapi,那么就需要网站同时支持2种认证方式,web页面的需要传统的cookie认证,webapi则需要使用jwt认证方式,两种默认情况下不能共存,一旦开启了jwt认证,cookie的登录界面都无法使用,原因是jwt是验证http head "Authorization" 这属性.所以连login页面都无法打开.
阿新
2022/05/06
1.5K0
完美解决asp.net core 3.1 两个AuthenticationScheme(cookie,jwt)共存在一个项目中
.NET 云原生架构师训练营(模块二 基础巩固 MongoDB API实现)--学习笔记
https://github.com/MINGSON666/Personal-Learning-Library/tree/main/ArchitectTrainingCamp/LighterApi
郑子铭
2021/01/06
3910
.NET 云原生架构师训练营(模块二 基础巩固 MongoDB API实现)--学习笔记
.NET 云原生架构师训练营(模块二 基础巩固 MongoDB API重构)--学习笔记
将业务从controller 抽取到 Lighter.Application 层,并为业务建立抽象接口 Lighter.Application.Contract层
郑子铭
2021/01/07
5700
.NET 云原生架构师训练营(模块二 基础巩固 MongoDB API重构)--学习笔记
推荐阅读
.NET 云原生架构师训练营(权限系统 代码实现 Identity)--学习笔记
4170
.NET 云原生架构师训练营(权限系统 代码实现 Store.EntityFramework)--学习笔记
2830
.NET 云原生架构师训练营(权限系统 代码实现 WebApplication)--学习笔记
3650
.NET 云原生架构师训练营(权限系统 代码实现 ActionAccess)--学习笔记
4090
.NET 云原生架构师训练营(权限系统 系统演示 EntityAccess)--学习笔记
2960
.NET 云原生架构师训练营(权限系统 系统演示 ActionAccess)--学习笔记
3580
.NET 云原生架构师训练营(权限系统 RGCA 开发任务)--学习笔记
3110
.NET 云原生架构师训练营(权限系统 系统演示 ActionAccess)--学习笔记
2570
.NET 云原生架构师训练营(权限系统 RGCA 开发任务)--学习笔记
3340
.NET 云原生架构师训练营(权限系统 系统演示 EntityAccess)--学习笔记
2320
IdentityServer4实战 - 基于角色的权限控制及Claim详解
2.7K0
ASP.NET Core Authentication and Authorization
1.3K0
ASP.NET Core 实战:基于 Jwt Token 的权限控制全揭露
2.6K0
ASP.NET MVC 随想录——探索ASP.NET Identity 身份验证和基于角色的授权,中级篇
4.5K0
实现一个登录:Mac+.NET 5+Identity+JWT+VS Code
6710
.NET 云原生架构师训练营(权限系统 RGCA 架构设计)--学习笔记
6220
.NET 云原生架构师训练营(权限系统 RGCA 架构设计)--学习笔记
6290
完美解决asp.net core 3.1 两个AuthenticationScheme(cookie,jwt)共存在一个项目中
1.5K0
.NET 云原生架构师训练营(模块二 基础巩固 MongoDB API实现)--学习笔记
3910
.NET 云原生架构师训练营(模块二 基础巩固 MongoDB API重构)--学习笔记
5700
相关推荐
.NET 云原生架构师训练营(权限系统 代码实现 Identity)--学习笔记
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档