using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using System.Security.Claims; using System.Text.RegularExpressions; using Znyc.CloudCar.Utility.Extensions; namespace Znyc.CloudCar.Auth.Policys { /// /// 权限授权处理器 /// public class PermissionHandler : AuthorizationHandler { /// /// 验证方案提供对象 /// public IAuthenticationSchemeProvider _schemes { get; set; } private readonly IHttpContextAccessor _accessor; /// /// 构造函数注入 /// /// /// /// public PermissionHandler(IAuthenticationSchemeProvider schemes, IHttpContextAccessor accessor) { _accessor = accessor; _schemes = schemes; } // 重写异步处理程序 protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement) { var httpContext = _accessor.HttpContext; if (!requirement.Permissions.Any()) { //var data = await _navigationRepository.QueryAsync(); //var list = (from item in data // where item.isLock == false // orderby item.id // select new PermissionItem // { // Url = item.linkUrl, // Role = item.identificationCode, // }).ToList(); //requirement.Permissions = list; } //请求Url if (httpContext != null) { var questUrl = httpContext.Request.Path.Value.ToLower(); //判断请求是否停止 var handlers = httpContext.RequestServices.GetRequiredService(); foreach (var scheme in await _schemes.GetRequestHandlerSchemesAsync()) { if (await handlers.GetHandlerAsync(httpContext, scheme.Name) is IAuthenticationRequestHandler handler && await handler.HandleRequestAsync()) { context.Fail(); return; } } //判断请求是否拥有凭据,即有没有登录 var defaultAuthenticate = await _schemes.GetDefaultAuthenticateSchemeAsync(); if (defaultAuthenticate != null) { var result = await httpContext.AuthenticateAsync(defaultAuthenticate.Name); //result?.Principal不为空即登录成功 if (result?.Principal != null) { // 将最新的角色和接口列表更新 // 这里暂时把代码移动到了Login获取token的api里,这样就不用每次都请求数据库,造成压力. // 但是这样有个问题,就是如果修改了某一个角色的菜单权限,不会立刻更新, // 需要让用户退出重新登录,如果你想实时更新,请把下边的注释打开即可. //var data = await _roleModulePermissionServices.RoleModuleMaps(); //var list = (from item in data // where item.IsDeleted == false // orderby item.Id // select new PermissionItem // { // Url = item.Module?.LinkUrl, // Role = item.Role?.Name, // }).ToList(); //requirement.Permissions = list; httpContext.User = result.Principal; //权限中是否存在请求的url //if (requirement.Permissions.GroupBy(g => g.Url).Where(w => w.Key?.ToLower() == questUrl).Count() > 0) //if (isMatchUrl) if (true) { // 获取当前用户的角色信息 var currentUserRoles = (from item in httpContext.User.Claims where item.Type == requirement.ClaimType select item.Value).ToList(); var isMatchRole = false; var permisssionRoles = requirement.Permissions.Where(w => currentUserRoles.Contains(w.Role)); foreach (var item in permisssionRoles) { try { if (Regex.Match(questUrl, item.Url?.ObjectToString().ToLower())?.Value == questUrl) { isMatchRole = true; break; } } catch (Exception) { // ignored } } //验证权限 //if (currentUserRoles.Count <= 0 || requirement.Permissions.Where(w => currentUserRoles.Contains(w.Role) && w.Url.ToLower() == questUrl).Count() <= 0) if (currentUserRoles.Count <= 0 || !isMatchRole) { context.Fail(); return; } } //判断过期时间(这里仅仅是最坏验证原则,你可以不要这个if else的判断,因为我们使用的官方验证,Token过期后上边的result?.Principal 就为 null 了,进不到这里了,因此这里其实可以不用验证过期时间,只是做最后严谨判断) if ((httpContext.User.Claims.SingleOrDefault(s => s.Type == ClaimTypes.Expiration)?.Value) != null && DateTime.Parse(httpContext.User.Claims.SingleOrDefault(s => s.Type == ClaimTypes.Expiration)?.Value) >= DateTime.Now) { context.Succeed(requirement); } else { context.Fail(); return; } return; } } //判断没有登录时,是否访问登录的url,并且是Post请求,并且是form表单提交类型,否则为失败 if (!questUrl.Equals(requirement.LoginPath.ToLower(), StringComparison.Ordinal) && (!httpContext.Request.Method.Equals("POST") || !httpContext.Request.HasFormContentType)) { context.Fail(); return; } } context.Succeed(requirement); } } }