1.IActionResult扩展定制
2.登录功能自定义鉴权授权类
3.ResourceFilter扩展定制
4.Filter的多种注册方式
5.自定义全局鉴权类,让某些控制器或方法不使用该鉴权
6.鉴权类带参数
7.自定义三个ActionFitler,分别注册全局/控制器/Action 执行顺序是怎么样的
8.如果想要控制Filter的执行顺序怎么办?
9.net自带鉴权授权(推荐,因为在俄罗斯套娃最外层,提前判断鉴权授权)
1.IActionResult扩展定制
#region IActionResult扩展 public JsonResult ToJSON() { var result = new JsonResult(new { Id = 123, Name = "海贝" }); result.ContentType = ""; return result; //return Json(new //{ // Id = 123, // Name = "海贝" //}); } [CustomAllowAnonymousAttribute] public FileResult VerifyCode() { string code = ""; Bitmap bitmap = VerifyCodeHelper.CreateVerifyCode(out code); base.HttpContext.Session.SetString("CheckCode", code); MemoryStream stream = new MemoryStream(); bitmap.Save(stream, ImageFormat.Gif); return File(stream.ToArray(), "image/gif"); } public string TostingConsole() { return ""; } public IActionResult ToNewtonsoftJsonResult() { return new NewtonsoftJsonResult(new { Id = 234, Name = "年轻人不讲武德" }); } public IActionResult ToXmlResult() { return new XmlResult("<a href='zhaoxiedu.net'>朝夕官网</a>"); } public void ToStringExtension() { HttpContext.Response.WriteAsync("<a href='zhaoxiedu.net'>朝夕官网</a>"); } public StringResult ToStringResult() { return new StringResult("<a href='zhaoxiedu.net'>朝夕官网</a>"); } public class NewtonsoftJsonResult : IActionResult { private object _Data = null; public NewtonsoftJsonResult(object data) { this._Data = data; } public async Task ExecuteResultAsync(ActionContext context) { HttpResponse response = context.HttpContext.Response; response.ContentType = "application/json;charset=utf-8"; await response.WriteAsync(Newtonsoft.Json.JsonConvert.SerializeObject(_Data)); } } public class XmlResult : IActionResult { private string _Data = null; public XmlResult(string data) { this._Data = data; } public async Task ExecuteResultAsync(ActionContext context) { HttpResponse response = context.HttpContext.Response; response.ContentType = "application/xml;charset=utf-8"; await response.WriteAsync(_Data); } } public class StringResult : IActionResult { private string _Data = null; public StringResult(string data) { this._Data = data; } public async Task ExecuteResultAsync(ActionContext context) { HttpResponse response = context.HttpContext.Response; response.ContentType = "text/plain;charset=utf-8"; await response.WriteAsync(_Data); } } /// <summary> /// 自定义扩展 /// 专门响应成一个Excel文件下载 /// 1.赋值对应的Content-type /// 2.写入文件流到Response /// </summary> public class ExcelResult : IActionResult //---NPOI { private object _Data = null; public ExcelResult(object data) { this._Data = data; } public async Task ExecuteResultAsync(ActionContext context) { HttpResponse response = context.HttpContext.Response; response.ContentType = "application/vnd.ms-excel"; //1.设置content-Type //2.根据数据生成excel文件 //3.把excel文件流写入到response //await response.WriteAsync(_Data); } } #endregion
2.鉴权授权(两步走1:自定义类,继承Attribute,2:相关方法注册鉴权类3.可选全局注册鉴权,全局实现AOP)
1.登录方法
[HttpPost] [CustomAllowAnonymousAttribute] public IActionResult Login(string name, string password, string verify) { string verifyCode = base.HttpContext.Session.GetString("CheckCode"); if (verifyCode != null && verifyCode.Equals(verify, StringComparison.CurrentCultureIgnoreCase)) { #region 这里在工作中的写法是去数据库中去做验证 if ("Zhaoxi".Equals(name) && "Zhaoxi".Equals(password))//就认为登录成功了 { #region Cookie/Session 自己写 一般使用Sessio为主 CurrentUser currentUser = new CurrentUser() { Id = 123, Name = "Zhaoxi", Account = "Administrator", Email = "1030499676", Password = "123456", LoginTime = DateTime.Now }; //写Session/写Cookies base.HttpContext.SetCookies("CurrentUser", Newtonsoft.Json.JsonConvert.SerializeObject(currentUser), 30); base.HttpContext.Session.SetString("CurrentUser", Newtonsoft.Json.JsonConvert.SerializeObject(currentUser)); #endregion return base.Redirect("/Fifth/Index"); } else { base.ViewBag.Msg = "账号密码错误"; } #endregion } else { base.ViewBag.Msg = "验证码错误"; } return View(); }
2.:创建自定义授权类,继承Attribute, IActionFilter
public class ActionAuthrizaFilterAttribute : Attribute, IActionFilter { /// <summary> /// 方法执行后 /// </summary> /// <param name="context"></param> public void OnActionExecuted(ActionExecutedContext context) { return; } /// <summary> /// 方法执行前 /// </summary> /// <param name="context"></param> public void OnActionExecuting(ActionExecutingContext context) { //要先判断,当前请求的Action上是否有标记 [CustomAllowAnonymousAttribute]特性 //如果有继续往后走,如果没有就不需要匿名 //应该检测两个地方:控制器和Action //判断action 上是否有CustomAllowAnonymousAttribute if (!context.ActionDescriptor.FilterDescriptors.Any(item => item.Filter is CustomAllowAnonymousAttribute)) { return; } //filterContext.ActionDescriptor.IsDefined(typeof(SkipCheckLoginAttribute) //判断控制器 上是否有CustomAllowAnonymousAttribute if (!context.Filters.Any(f => f is CustomAllowAnonymousAttribute)) { return;//匿名 不检查 } ///获取session CurrentUser currentUser = context.HttpContext.GetCurrentUserBySession(); //因为每一次请求来了都要先进入到这儿来,所有,如果有些Action需要避开鉴权;要判断如果标记的是AllowAnonymous,就跳过 if (currentUser == null)// 就要拦截 { if (this.IsAjaxRequest(context.HttpContext.Request)) { context.Result = new JsonResult(new { Success = false, Messge = "没有权限" }); } context.Result = new RedirectResult("~/fifth/Login");//短路器,只要对context.Result赋值,就不再往后执行 } //继续往后 } private bool IsAjaxRequest(HttpRequest request) { string header = request.Headers["X-Requested-With"]; return "XMLHttpRequest".Equals(header); } }
3.:加在需要做授权的方法上
4全局注册:鉴权类 对整个系统所有acction都生效
5做了全局生效 如果某些方法不需要生效
1.就自定义空的一个类 继承Attribute
2.然后在鉴权的类,判断有没有这个类,有就不做鉴权认证
public class CustomAllowAnonymousAttribute:Attribute { }
6.鉴权类如果需要带参数
public class ActionAuthrizaFilterAttribute : Attribute, IActionFilter { #region 需要调用其他服务 //如果需要使用第三方服务:大家切记,要通过依赖注入来完成; private readonly ILogger<ActionAuthrizaFilterAttribute> _ILogger = null; public ActionAuthrizaFilterAttribute(ILogger<ActionAuthrizaFilterAttribute> logger) { this._ILogger = logger; } #endregion //请求来了以后,是要去访问某一个Action; //在之前要执行ActionFilter---需要实例化Filter //获取当前请求的Controller名称和Action名称,保存起来; //权限验证通过以后,就跳转到当前路径下去 private string currentPath = null; /// <summary> /// 方法执行后 /// </summary> /// <param name="context"></param> public void OnActionExecuted(ActionExecutedContext context) { return; } /// <summary> /// 方法执行前 /// </summary> /// <param name="context"></param> public void OnActionExecuting(ActionExecutingContext context) { Console.WriteLine("开始验证权限。。。。"); //要先判断,当前请求的Action上是否有标记 [CustomAllowAnonymousAttribute]特性 //如果有继续往后走,如果没有就不需要匿名 //应该检测两个地方:控制器和Action //判断action 上是否有CustomAllowAnonymousAttribute //在.NET5中这里获取特性判断,不一样了;在EndpointMetadata获取 //1.无论是标记在Controller上的自定义特性还是标记在Action上的自定义特性能,在这里都是统一通过context.ActionDescriptor.EndpointMetadata来获取 if (context.ActionDescriptor.EndpointMetadata.Any(item => item is CustomAllowAnonymousAttribute)) { return; } ////filterContext.ActionDescriptor.IsDefined(typeof(SkipCheckLoginAttribute) ////判断控制器 上是否有CustomAllowAnonymousAttribute //if (context.Filters.Any(f => f is CustomAllowAnonymousAttribute)) //{ // return;//匿名 不检查 //} ///获取session CurrentUser currentUser = context.HttpContext.GetCurrentUserBySession(); //因为每一次请求来了都要先进入到这儿来,所有,如果有些Action需要避开鉴权;要判断如果标记的是AllowAnonymous,就跳过 if (currentUser == null)// 就要拦截 { _ILogger.LogInformation($"{currentUser?.Name}权限验证通过了。。。"); if (this.IsAjaxRequest(context.HttpContext.Request)) { context.Result = new JsonResult(new { Success = false, Messge = "没有权限" }); } context.Result = new RedirectResult("~/fifth/Login");//短路器,只要对context.Result赋值,就不再往后执行 } //继续往后 } private bool IsAjaxRequest(HttpRequest request) { string header = request.Headers["X-Requested-With"]; return "XMLHttpRequest".Equals(header); } }
7.定义三个ActionFitler,分别注册全局/控制器/Action
默认执行顺序如何?
如下:
1.全局方法执行前
2.控制器方法执行前
3.ActionFilter方法执行前
4. 执行方法
5.ActionFilter方法执行后
6.控制器方法执行后
7.全局方法执行后
8.如果想要控制Filter的执行顺序怎么办?
9.net自带鉴权授权(推荐,因为在俄罗斯套娃最外层,提前判断鉴权授权)
第一步:在在startup.cs中间件里面 添加相关代码
第二步:在在startup.cs中间件里面 添加鉴权授权相关支持
第三步:登录成功之后写入用户信息
[HttpPost] [CustomAllowAnonymousAttribute] public IActionResult Login(string name, string password, string verify) { string verifyCode = base.HttpContext.Session.GetString("CheckCode"); if (verifyCode != null && verifyCode.Equals(verify, StringComparison.CurrentCultureIgnoreCase)) { #region 这里在工作中的写法是去数据库中去做验证 if ("Zhaoxi".Equals(name) && "Zhaoxi".Equals(password))//就认为登录成功了 { #region Cookie/Session 自己写 一般使用Sessio为主 //CurrentUser currentUser = new CurrentUser() //{ // Id = 123, // Name = "Zhaoxi", // Account = "Administrator", // Email = "1030499676", // Password = "123456", // LoginTime = DateTime.Now //}; ////写Session/写Cookies //base.HttpContext.SetCookies("CurrentUser", Newtonsoft.Json.JsonConvert.SerializeObject(currentUser), 30); //base.HttpContext.Session.SetString("CurrentUser", Newtonsoft.Json.JsonConvert.SerializeObject(currentUser)); #endregion #region 鉴权:鉴权,检测有没有登录,登录的是谁,赋值给User //每一个Claim 表示记录当前用户的某一个信息(票证) //这部分角色有可能是从数据库中查询出来的;---对应当前用户登录的时候,动态查询出的角色信息 var rolelist = new List<string>() { "Admin", "Teacher", "Student" }; var claims = new List<Claim>()//鉴别你是谁,相关信息 { new Claim(ClaimTypes.Role,"Admin"), new Claim(ClaimTypes.Name,name), new Claim("password",password),//可以写入任意数据 new Claim("Account","Administrator"), new Claim("role","admin"), new Claim("zhaoxi","zhaoxi") }; foreach (var role in rolelist) { claims.Add(new Claim(ClaimTypes.Role, role)); } //ClaimsPrincipal 用户信息打包,写入到一个身份证中去 ClaimsPrincipal userPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims, "Customer")); //针对与当前用户的身份证 //登录//把身份证信息保存到Cookies中 HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, userPrincipal, new AuthenticationProperties { ExpiresUtc = DateTime.UtcNow.AddMinutes(30),//过期时间:30分钟 }).Wait();//没用await #endregion var user = HttpContext.User; return base.Redirect("/Fifth/Index"); } else { base.ViewBag.Msg = "账号密码错误"; } #endregion } else { base.ViewBag.Msg = "验证码错误"; } return View(); }
第四步:标记特性,给需要做鉴权的地方acction支持鉴权
10.多角色授权比较死板
//这部分角色有可能是从数据库中查询出来的;---对应当前用户登录的时候, //动态查询出的角色信息 var rolelist = new List<string>() { "Admin", "Teacher", "Student" }; var claims = new List<Claim>()//鉴别你是谁,相关信息 { new Claim(ClaimTypes.Role,"Admin"),//角色授权 new Claim(ClaimTypes.Name,name), new Claim("password",password),//可以写入任意数据 new Claim("Account","Administrator"), new Claim("role","admin"), new Claim("zhaoxi","zhaoxi") }; foreach (var role in rolelist) { claims.Add(new Claim(ClaimTypes.Role, role)); }
//角色鉴权 [Authorize(Roles ="Admin")] public ViewResult Index01() { _Logge.LogInformation("在这里执行了Action"); return View(); } [Authorize(Roles = "Admin,Teacher")] public ViewResult Index02() { _Logge.LogInformation("在这里执行了Action"); return View(); } [Authorize(Roles = "Admin,Teacher,Student")] public ViewResult Index03() { _Logge.LogInformation("在这里执行了Action"); return View(); } [Authorize(Roles = "Admin,Teacher,Student")] //1.标记Authorze 指定角色以逗号分隔:只要是包含登录的时候写入的某一个角色;就通过验证;或者的关系 [Authorize(Roles = "ABCCC")]//2.如果标记多个特性,且的关系 public ViewResult Index04() { _Logge.LogInformation("在这里执行了Action"); return View(); } [Authorize] public ViewResult Index05() { _Logge.LogInformation("在这里执行了Action"); return View(); }
//支持角色验证 services.AddAuthorization(options => { //增加当前角色 options.AddPolicy("CustomAuthorizePolicy", policy => { //这里表示生效的时候,一些验证逻辑 policy.RequireRole("Admin,Teacher,Student"); }); });
11.自定义策略认证(推荐)
public class CustomAuthorizationHandler : AuthorizationHandler<CustomAuthorizationRequirement> { public CustomAuthorizationHandler() { } protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CustomAuthorizationRequirement requirement) { if (requirement.Name == "Policy01") { ///策略1的逻辑 } if (requirement.Name == "Policy02") { ///策略1的逻辑 } if (true) { } //在这里可以自己定义自己的规则 { //去数据库里面查出所有角色想信息 //其实这里可以去数据库里面去做一些查询,然后根据用户的信息,做计算;如果符合就context.Succeed(requirement); //否则就Task.CompletedTask; } //context.User 鉴权成功(登录成功以后),用户的信息; var role = context.User.FindFirst(c => c.Value.Contains("admin")); if (role != null) { context.Succeed(requirement); //验证通过了 } return Task.CompletedTask; //验证不同过 } } }
public class CustomAuthorizationRequirement: IAuthorizationRequirement { /// <summary> /// /// </summary> public CustomAuthorizationRequirement(string policyname) { this.Name = policyname; } public string Name { get; set; } }
[Authorize(policy: "customPolicy")] public ViewResult Index06() { _Logge.LogInformation("在这里执行了Action"); return View(); } [Authorize(policy: "customPolicy02")] [Authorize(policy: "customPolicy01")] public ViewResult Index07() { _Logge.LogInformation("在这里执行了Action"); return View(); }
////支持策略认证 //services.AddAuthorization(options => //{ // options.AddPolicy("customPolicy", polic => // { // polic.AddRequirements(new CustomAuthorizationRequirement("Policy01"));//指定策略 // }); //}); //services.AddAuthorization(options => //{ // options.AddPolicy("customPolicy01", polic => // { // polic.AddRequirements(new CustomAuthorizationRequirement("Policy02")); // }); //}); //services.AddAuthorization(options => //{ // options.AddPolicy("customPolicy02", polic => // { // polic.AddRequirements(new CustomAuthorizationRequirement("Policy03")); // }); //}); //在这里标记自定义的授权Hanlder生效 services.AddSingleton<IAuthorizationHandler, CustomAuthorizationHandler>();
角色认证+策步骤:
1.登录时写入角色claimtypes.role
2.在startup中支持角色认证
/3.在标记action/控制器的时候增加authorize 参数role,
4.标记一个,多个role分隔,是或者关系,如果标记多个authorize,roles是且的关系
自定义步骤
策略验证;验证的时候自定义策略,再action 控制器使用策略;策略自己定义
1.定义权限验证的执行类,实现authorizationhandler抽象
2.在starup注册ioc服务------在action/控制器/全局标记authorize以后,在请求action时,就会先进入到定义的权限验证类中去执行handlerequirementasync
3.验证方式有很多种; 每一种可以定义一种策略 实现iauthorizationrequirement接口
4.定义的策略要在验证逻辑中使用;可以当authorizationhandler 的泛型参数;
12.ResourceFilter做缓存AOP+或者可以把缓存缓存redis
第一步自定义 缓存类 继承 Attribute
public class CustomResourceFilterAttribute : Attribute, IResourceFilter { /// <summary> /// 可以把Redis的操作类库给注入进来,保存缓存就保存到Redis中去。。。。 /// </summary> public CustomResourceFilterAttribute() { } private static Dictionary<string, object> CacheDictionary = new Dictionary<string, object>(); /// <summary> /// 在XX资源之前 /// </summary> /// <param name="context"></param> public void OnResourceExecuting(ResourceExecutingContext context) { ///1.先判断缓存中是否有数据 ///2.如果有数据,就直接拿着数据走了 ///3.没有就继续往后,就去实例化控制器,去执行Action做计算 //缓存:需要一个key; 只要是key不变,拿到的数据就不变; //如果做缓存,一般请求路径不变,数据一般不变; string key = context.HttpContext.Request.Path; if (CacheDictionary.Any(item=>item.Key==key)) { context.Result = CacheDictionary[key] as IActionResult; } //如果没有缓存---就继续往后; Console.WriteLine("CustomResourceFilterAttribute.OnResourceExecuting"); } /// <summary> /// 在xx资源之后 /// </summary> /// <param name="context"></param> public void OnResourceExecuted(ResourceExecutedContext context) { //代码执行到这里,就表示一定完成了逻辑计算;就有结果; string key = context.HttpContext.Request.Path; CacheDictionary[key] = context.Result; Console.WriteLine("CustomResourceFilterAttribute.OnResourceExecuted"); } }
第二步控制器添加特性
[CustomResourceFilterAttribute] public IActionResult Index() { TestServiceException testServiceException = new TestServiceException(); testServiceException.Show(); ViewBag.date = DateTime.Now; return View(); }
缓存:在第一次请求数据以后,把计算结果保存在内存----
下一次再来请求的时候,只要是还是要获取之前的数据;就直接取内存数据;不用做计算了;提高性能;
1.定义一个ResourceFilter 实现IResourceFilter接口
2.实现方法,标记在action上
执行顺序:
1.ResourceFilter---OnResourceExecuting
2.执行控制器构造函数
3.执行Action
4.ResourceFilter---OnResourceExecuted
13.异常AOP ExceptionFilter
第一步:自定义异常类 继承Attribute
public class CustomExceptionFilterAttribute : Attribute, IExceptionFilter { private readonly IModelMetadataProvider _modelMetadataProvider; public CustomExceptionFilterAttribute(IModelMetadataProvider modelMetadataProvider) { _modelMetadataProvider = modelMetadataProvider; } /// <summary> /// 当异常发生的时候就触发 /// </summary> /// <param name="context"></param> public void OnException(ExceptionContext context) { //判断异常是否被处理了 if (!context.ExceptionHandled) { if (this.IsAjaxRequest(context.HttpContext.Request))//header看看是不是XMLHttpRequest { context.Result = new JsonResult(new { Result = false, Msg = context.Exception.Message });//中断式---请求到这里结束了,不再继续Action } else { var result = new ViewResult { ViewName = "~/Views/Shared/Error.cshtml" }; result.ViewData = new ViewDataDictionary(_modelMetadataProvider, context.ModelState); result.ViewData.Add("Exception", context.Exception); context.Result = result; //断路器---只要对Result赋值--就不继续往后了; } context.ExceptionHandled = true; } } private bool IsAjaxRequest(HttpRequest request) { string header = request.Headers["X-Requested-With"]; return "XMLHttpRequest".Equals(header); } }
第二步:全局注册
services.AddMvc(option => { //3.全局注册 对当前项目所有Action都生效 //option.Filters.Add(new ActionAuthrizaFilterAttribute()); //option.Filters.Add<ActionAuthrizaFilterAttribute>();//也可以支持以来注入 option.Filters.Add<GlobalActionFilterAttribute>();//全局注册 option.Filters.Add<CustomExceptionFilterAttribute>(); //全局异常都可以捕捉 //option.Filters.Add<Authorize>(); });
第三步:增加异常友好页面
@*@model ErrorViewModel*@ @{ ViewData["Title"] = "Error"; } @{ ViewData["Title"] = "Error"; Exception exception = base.ViewData["Exception"] as Exception; } <h1 class="text-danger">Error.</h1> <h2 class="text-danger">An error occurred while processing your request.</h2> <h1>@exception.Message</h1> <h3>Development Mode</h3> <p> Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred. </p> <p> <strong>The Development environment shouldn't be enabled for deployed applications.</strong> It can result in displaying sensitive information from exceptions to end users. For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong> and restarting the app. </p>
第四步:有点错误补充不到 增加补充错误措施
#region 捕捉异常的补充 app.UseStatusCodePagesWithReExecute("/Home/Error/{0}");//只要不是200 都能进来 app.UseExceptionHandler(errorApp => { errorApp.Run(async context => { context.Response.StatusCode = 200; context.Response.ContentType = "text/html"; await context.Response.WriteAsync("<html lang="en"><body> "); await context.Response.WriteAsync("ERROR!<br><br> "); var exceptionHandlerPathFeature = context.Features.Get<IExceptionHandlerPathFeature>(); Console.WriteLine("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&"); Console.WriteLine($"{exceptionHandlerPathFeature?.Error.Message}"); Console.WriteLine("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&"); if (exceptionHandlerPathFeature?.Error is FileNotFoundException) { await context.Response.WriteAsync("File error thrown!<br><br> "); } await context.Response.WriteAsync("<a href="/">Home</a><br> "); await context.Response.WriteAsync("</body></html> "); await context.Response.WriteAsync(new string(' ', 512)); // IE padding }); }); #endregion
T:能正常捕获,F:捕获不了
14.ResultFilter 结果AOP,呈现结果之前的AOP
第一步:
public class CustomoResultFilterAttribute : Attribute, IResultFilter { public void OnResultExecuting(ResultExecutingContext context) { Console.WriteLine("CustomoResultFilterAttribute.OnResultExecuting"); } public void OnResultExecuted(ResultExecutedContext context) { Console.WriteLine("CustomoResultFilterAttribute.OnResultExecuted"); } } public class CustomoAsyncResultFilterAttribute : Attribute, IAsyncResultFilter { public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next) { { Console.WriteLine("结果执行前"); } await next.Invoke(); //执行结果 { Console.WriteLine("结果执行后"); } } } public class CustomoResultFromParentFilterAttribute : ResultFilterAttribute { public override void OnResultExecuted(ResultExecutedContext context) { base.OnResultExecuted(context); } public override void OnResultExecuting(ResultExecutingContext context) { base.OnResultExecuting(context); } public override Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next) { return base.OnResultExecutionAsync(context, next); } }