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

目录

  • 模块拆分
  • 代码重构

模块拆分

image.png

代码重构

  • AuthenticationController
  • PermissionController
  • IAuthorizationMiddlewareResultHandler
  • ISaveChangesInterceptor

AuthenticationController

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

登录

代码语言:txt
复制
[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 &amp;&amp; await _userManager.CheckPasswordAsync(user, model.Password))  
{  
    var userRoles = await _userManager.GetRolesAsync(user);  

    var authClaims = new List&lt;Claim&gt;  
    {  
        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[&#34;JWT:Secret&#34;]));  

    var token = new JwtSecurityToken(  
        issuer: _configuration[&#34;JWT:ValidIssuer&#34;],  
        audience: _configuration[&#34;JWT:ValidAudience&#34;],  
        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
复制
[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
复制
[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
复制
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 &gt; 0)
        {
            foreach (var scheme in policy.AuthenticationSchemes)
            {
                await context.ChallengeAsync(scheme);
            }
        }
        else
        {
            await context.ChallengeAsync();
        }

        return;
    }
    else if (authorizeResult.Forbidden)
    {
        if (policy.AuthenticationSchemes.Count &gt; 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
复制
public class SavingChangeInterceptor: ISaveChangesInterceptor
{
...

public async ValueTask&lt;InterceptionResult&lt;int&gt;&gt; SavingChangesAsync(DbContextEventData eventData, InterceptionResult&lt;int&gt; result,
    CancellationToken cancellationToken = new CancellationToken())
{
    var contextName = eventData.Context.GetType().Name;
    var permissions = await _permissionManager.GetByGroupAsync(contextName);
    var entityPermissions = permissions.Select(p =&gt; new EntityPermission(p)).ToList();

    
    if (permissions==null || !permissions.Any())
        return result;

    var addedEntities = eventData.Context.ChangeTracker.Entries().Where(e =&gt; e.State == EntityState.Added);
    var deletedEntties = eventData.Context.ChangeTracker.Entries().Where(e =&gt; e.State == EntityState.Deleted);
    var modifiedEntities = eventData.Context.ChangeTracker.Entries().Where(e =&gt; e.State == EntityState.Modified);

    await CheckAddedEntitiesAsync(addedEntities, entityPermissions);
    await CheckDeletedEntitiesAsync(deletedEntties,entityPermissions);
    await CheckModifiedEntitiesAsync(modifiedEntities,entityPermissions);
    
    return result;
}

private async Task CheckAddedEntitiesAsync(IEnumerable&lt;EntityEntry&gt; entities,IEnumerable&lt;EntityPermission&gt; permissions)
{
    foreach (var entity in entities)
    {
        var entityName = entity.Metadata.Name;
        var entityPermissions = permissions.Where(p =&gt; 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 =&gt; e.Data.Create).Select(e =&gt; e.Key);
        var claimValues = user.Claims.Where(c =&gt; c.Type == ClaimsTypes.Permission).Select(c =&gt; c.Value);
        if (!createPermission.Intersect(claimValues).Any())
            throw new AuthorizationException();

    }
}

private async Task CheckDeletedEntitiesAsync(IEnumerable&lt;EntityEntry&gt; entities,IEnumerable&lt;EntityPermission&gt; permissions)
{
    foreach (var entity in entities)
    {
        var entityName = entity.Metadata.Name;
        var entityPermissions = permissions.Where(p =&gt; 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 =&gt; e.Data.Delete).Select(e =&gt; e.Key);
        var claimValues = user.Claims.Where(c =&gt; c.Type == ClaimsTypes.Permission).Select(c =&gt; c.Value);
        if (!deletePermission.Intersect(claimValues).Any())
            throw new AuthorizationException();

    }
}

private async Task CheckModifiedEntitiesAsync(IEnumerable&lt;EntityEntry&gt; entities,IEnumerable&lt;EntityPermission&gt; permissions)
{
    foreach (var entity in entities)
    {
        var entityName = entity.Metadata.Name;
        var entityPermissions = permissions.Where(p =&gt; 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 =&gt; m.IsModified).Select(m =&gt; m.Metadata.Name);
        var canUpdatePermissionKeys = new List&lt;string&gt;();

        foreach (var permission in entityPermissions)
        {
            var definedMembers = permission.Data.Members.Where(m =&gt; m.Update &amp;&amp; modifiedMembers.Contains(m.MemberName));
            if(definedMembers.Any())
                canUpdatePermissionKeys.Add((permission.Key));
        }

        var claimValues = user.Claims.Where(c =&gt; c.Type == ClaimsTypes.Permission).Select(c =&gt; c.Value);
        if (!canUpdatePermissionKeys.Intersect(claimValues).Any())
            throw new AuthorizationException();
    }
}

...

}

GitHub源码链接:

https://github.com/MingsonZheng/dotnetnb.security refactor 分支