一、实现
1、Permission文件
代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Blog.Jwt
{
/// <summary>
/// 用户或角色或其他凭据实体
/// </summary>
public class Permission
{
/// <summary>
/// 用户或角色或其他凭据名称
/// </summary>
public virtual string RoleName{ get; set; }
/// <summary>
/// 请求Url
/// </summary>
public virtual string Url{ get; set; }
}
}
如图所示:

2、PermissionHandler.cs
代码如下:
using Blog.Jwt;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Security.Claims;
using System.Threading.Tasks;
namespace Blog.Jwt
{
/// <summary>
/// 权限授权Handler
/// </summary>
public class PermissionHandler : AuthorizationHandler<PermissionRequirement>//我们自定义用户属性,并自己验证
{
/// <summary>
/// 验证方案提供对象
/// </summary>
public IAuthenticationSchemeProvider Schemes { get; set; }
private readonly IHttpContextAccessor _accessor;
/// <summary>
/// 构造函数注入
/// </summary>
/// <param name="schemes"></param>
/// <param name="accessor"></param>
public PermissionHandler(IAuthenticationSchemeProvider schemes, IHttpContextAccessor accessor)
{
Schemes = schemes;
_accessor = accessor;
}
/// <summary>
/// 摘要:根据特定需求决定是否允许授权 特定就是add参数,但是未包含在创建token属性内
///
/// 二点注意:
/// 一、没必要重写加参数,我们可以从上下文中获取到token(老张完全多此一句) 我们完全可以从token获取用户信息并对比 requirement完全多余
/// 二、HandleRequirementAsync用了 这个方法 没有办法HttpContext.Response.WriteAsync,不然会报错报错{StatusCode cannot be set because the response has already started.}因为响应已经开始
/// 所以我们尽量不在此方法里面写,我们只需要知道时间过期则请求头部 context.Response.Headers.Add("Token-Expired", "true"); 即可,其他一律为未授权
/// 其实我很想 提示 未授权和认证上失败的 区分下
///
/// 第二次回顾
/// 1、具体提示信息既然无法返回 body内容,就返回状态码(这个是可行的),有个全局拦截来根据返回自定义状态码处理,状态码状态信息是对外一致(也是全局拦截的统一状态码信息的好处)
/// 2、我之前认为说为什么add参数呢,甚至你不用系统提供的auth,直接写个中间件一样能实现。微软这么设计,是为了更复杂的抽象层而已。
///
/// 我还是选择不继承 AuthorizationHandler ,不添加add 参数,直接用
///
/// reqirement 用来定义授权认证的参数 拿他来验证的
///
/// </summary>
/// <param name="context"></param>
/// <param name="requirement"></param>
/// <returns></returns>
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
{
#region 想返回具体提示
/*
if (!_accessor.HttpContext.Response.HasStarted)//先判断context.Response.HasStarted
{
var results = JsonConvert.SerializeObject(new ApiResultModels { Success = false, Message = "测试返回结果", Code = "406" });
await _accessor.HttpContext.Response.WriteAsync(results);
//写入后报错{StatusCode cannot be set because the response has already started.}因为响应已经开始
if (true)
{
context.Fail();
return CompletedTask;
}
} //想返回想要的返回具体结果比如 过期时间提示token已过期、Audience颁布者则提示token无效!
只能通过设置状态码解决
_accessor.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;//403
context.Fail();
if (true)
*/
#endregion
////赋值用户权限
//从AuthorizationHandlerContext转成HttpContext,以便取出表求信息
//var httpContext = (context.Resource as Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext).HttpContext;
var httpContext = _accessor.HttpContext;
/*//动态获取权限项目
if (!requirement.Permissions.Any())
{
var data = await _role.GetPermissions();
var list = (from item in data
where item.IsDeleted = false
orderby item.Id
select new Permission
{
Url = "",
Name = ""
}).ToList();
requirement.Permissions = list;
}*/
//请求Url
var questUrl = httpContext.Request.Path.Value.ToLower();
#region 判断请求是否停止
//判断请求是否停止
var handlers = httpContext.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();// IAuthenticationSchemeProvider 用来提供对Scheme的注册和查询
foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync())//按请求处理的优先级顺序返回方案(Scheme)。
{
//来获取指定的Scheme的Hander
var handler = await handlers.GetHandlerAsync(httpContext, scheme.Name) as IAuthenticationRequestHandler;
if (handler != null && await handler.HandleRequestAsync()) //handler.HandleRequestAsync 如果请求处理应停止,则返回true
{
context.Fail();
return;
}
}
//后台 怎么根据 HttpContext判断请求是否停止?(保留疑问)
#endregion
//判断请求是否拥有凭据,即有没有登录 GetDefaultAuthenticateSchemeAsync 是获取默认的授权方案信息
var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();//public IAuthenticationSchemeProvider Schemes 验证方案提供对象 1、获取认证方案实例对象不为null
if (defaultAuthenticate == null)
throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultAuthenticateScheme found.");
else
{
//扩展方法 public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext context, string scheme) 2、context上下文通过身份验证方案的名称,获取认证方案信息
//判断使用是否授权
var result = await httpContext.AuthenticateAsync(defaultAuthenticate.Name);//身份认证 defaultAuthenticate.Name==Bearer(header里面有Bearer)
//result?.Principal不为空即登录成功
if (result?.Principal != null)
{
httpContext.User = result.Principal;//3、token字符串
var strToken = result.Properties.Items.FirstOrDefault().Value;//token字符串
//权限中是否存在请求的url
if (requirement.Permissions.GroupBy(g => g.Url).Where(w => w.Key?.ToLower() == questUrl).Count() > 0)
{
// 获取当前用户的角色信息
var currentUserRoles = (from item in httpContext.User.Claims
where item.Type == requirement.ClaimType
select item.Value).ToList();
//验证权限 失败则
if (currentUserRoles.Count <= 0 || requirement.Permissions.Where(w => currentUserRoles.Contains(w.RoleName) && w.Url.ToLower() == questUrl).Count() <= 0)
{
// 可以在这里设置跳转页面,不过还是会访问当前接口地址的
//httpContext.Response.Redirect(requirement.DeniedAction);
_accessor.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;//401 未授权
context.Fail();
//if (true)
return;
}
}
else
{
//context.Fail();
//return;
_accessor.HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;//404 此url不存在
context.Fail();
//if (true)
return;
}
//判断过期时间
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.UtcNow)
{
context.Succeed(requirement);
}
else
{
context.Fail();
return