
新增 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 新增 创建实体权限,用户角色相关接口
[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();}在 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 是 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(); } } ...}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) 。