.NET 雲原生架構師訓練營(許可權系統 程式碼重構)--學習筆記

MingsonZheng發表於2022-02-21

目錄

  • 模組拆分
  • 程式碼重構

模組拆分

程式碼重構

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

相關文章