找回密码
 立即注册
首页 业界区 安全 c#.net的学习(二)

c#.net的学习(二)

鞍汉 4 天前
ASP.CORE的学习(二)

Filter的使用

简单来说,过滤器的作用和意义在于提供一种机制,用于在请求处理管道的特定阶段执行代码,从而优雅地处理那些“横切关注点” (Cross-Cutting Concerns)。
常见的过滤器的执行顺序
请求 -> [授权] -> [资源] -> [操作] -> Action方法 -> [操作] -> [结果] -> [资源] -> 响应
异常过滤器像一个安全网,在上述任何一个环节(授权之后)出错时都会被触发。
ResoureFilter


  • 接口:IResourceFilter和IAsyncResourceFilter
  • 执行时机:在授权之后,但在模型绑定之前运行。它会"环绕"后续大部分管道,所以在Action执行后还会再次执行。
  • 核心作用:非常适合用于缓存或任何需要在管道早期"短路"以避免执行Action的场景。
  • 常见用途:

    • 实现输出缓存:在请求到达时,检查缓存中是否有结果。如果命中,则直接返回缓存内容,后续的模型绑定、Action的执行等全部跳过,性能极高。
    • 请求预处理:在模型绑定前对请求做一些全局性的处理。
    • 向响应中添加全局Header:在管道末尾,向所有响应中添加一个公共响应头(如X-Version)

  • 一句话总结:"缓存和资源的管家",能在第一时间决定是否需要干活,并负责善后。
同步的例子:
由于是演示,没有使用内置缓存
实现同步的Filter的代码CustomResourceFilterAttribute.cs
  1. public class CustomResourceFilterAttribute : Attribute, IResourceFilter
  2. {
  3.     private static Dictionary<string, Object> CacheDictionary = new Dictionary<string, Object>();
  4.     public void OnResourceExecuting(ResourceExecutingContext context)
  5.     {
  6.         string key = context.HttpContext.Request.Path.ToString().ToLower();
  7.         Console.WriteLine($"CustomResourceFilter: OnResourceExecuting - Checking cache for key: {key}");
  8.         if (CacheDictionary.TryGetValue(key, out var cachedValue))
  9.         {
  10.             Console.WriteLine("CustomResourceFilter: Cache hit - Returning cached response.");
  11.             context.Result = (Microsoft.AspNetCore.Mvc.IActionResult)cachedValue;
  12.         }
  13.     }
  14.     public void OnResourceExecuted(ResourceExecutedContext context)
  15.     {
  16.         string key = context.HttpContext.Request.Path.ToString().ToLower();
  17.         if (!CacheDictionary.ContainsKey(key))
  18.         {
  19.             Console.WriteLine($"CustomResourceFilter: OnResourceExecuted - Caching response for key: {key}");
  20.             CacheDictionary[key] = context.Result;
  21.         }
  22.     }
  23. }
复制代码
在控制器的代码
  1.   [Route("api/[controller]")]
  2.   [ApiController]
  3.   public class FilterController : ControllerBase
  4.   {
  5.       [HttpGet("Index"),CustomResourceFilter]
  6.       public IActionResult Index()
  7.       {
  8.           // 直接返回一个简单的字符串响应
  9.           // 在 Action 中打印日志,用于判断 Action 是否被执行
  10.           Console.WriteLine(">>> Action Executed: Generating new server time...");
  11.           // 返回一个每次都会变化的内容
  12.           var result = new
  13.           {
  14.               message = "This is a fresh response from the server1.",
  15.               serverTime = DateTime.Now.ToString("o") // "o" 格式包含毫秒,便于观察
  16.           };
  17.           return Ok(result);
  18.       }
  19.   }
复制代码
这样在用户访问的时候就会返回固定的时间戳。
异步版本
  1. public class CustomResourceAsyncFilterAttribute:Attribute,IAsyncResourceFilter
  2. {
  3.      private static ConcurrentDictionary<string, Object> CacheDictionary = new ConcurrentDictionary<string, Object>();
  4.      public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
  5.      {
  6.          string key = context.HttpContext.Request.Path.ToString().ToLower();
  7.          Console.WriteLine($"AsyncCustomResourceFilter: OnResourceExecuting - Checking cache for key: {key}");
  8.          // 1. 在 Action 执行前,检查缓存
  9.          if (CacheDictionary.TryGetValue(key, out var cachedValue))
  10.          {
  11.              Console.WriteLine("AsyncCustomResourceFilter: Cache hit - Returning cached response.");
  12.              // 缓存命中,直接设置结果并短路,不调用 next()
  13.              context.Result = (IActionResult)cachedValue;
  14.              return; // 直接返回,后续代码不执行
  15.          }
  16.          // 2. 缓存未命中,调用 next() 来执行后续的管道(包括 Action)
  17.          // next() 返回一个 ResourceExecutedContext,包含了执行完成后的结果
  18.          ResourceExecutedContext executedContext = await next();
  19.          // 3. 在 Action 执行后,将结果存入缓存
  20.          // 确保请求成功且结果不为空
  21.          if (executedContext.Result != null)
  22.          {
  23.              Console.WriteLine($"AsyncCustomResourceFilter: OnResourceExecuted - Caching response for key: {key}");
  24.              CacheDictionary[key] = executedContext.Result;
  25.          }
  26.      }
  27. }
复制代码
控制器的代码
  1. [HttpGet("ResourceAsyncFilter"), CustomResourceAsyncFilter]
  2. public async Task<IActionResult> ResourceAsyncFilter()
  3. {
  4.      // 直接返回一个简单的字符串响应
  5.      // 在 Action 中打印日志,用于判断 Action 是否被执行
  6.      Console.WriteLine(">>> Async Action Executed: Generating new server time...");
  7.      // 模拟一个异步操作,例如数据库查询或调用外部 API
  8.      await Task.Delay(100); // 暂停 100 毫秒
  9.      var result = new
  10.      {
  11.          message = "This is a fresh ASYNC response from the server.",
  12.          serverTime = DateTime.Now.ToString("o")
  13.      };
  14.      return Ok(result);
  15. }
复制代码
操作过滤器


  • 接口:IActionFilter/IAsyncFilter
  • 执行的时机:在模型绑定完成之后,紧接着Action方法执行的前后运行。
  • 核心的作用:这是最常用的过滤器,因为它能访问到已经绑定和验证好的模型数据(ActionExecutingContext.ActionArguments),并且能操纵Action的执行结果。
  • 常见的用途:

    • 模型状态验证:检查ModelState.IsValid,如果模型数据无效,则提前返回400 Bad Request.
    • 记录Action的执行日志,记录Action的执行时间传入传出的参数等。
    • 修改Action参数:在Action执行前,对参数进行一些修改或增强。
    • 修改Action结果:在Action执行后,对返回的IActionResult进行修改。

  • 一句话总结:Action的方法贴身助理,负责处理与Action执行直接相关的杂务。
    同步版的代码:
  1. public void OnActionExecuting(ActionExecutingContext context)
  2. {
  3.     context.HttpContext.Items["start"]=Stopwatch.StartNew();
  4.     Console.WriteLine("CustomActionFilter: OnActionExecuting - Action is about to execute.");
  5.     // 可以在这里添加自定义逻辑,比如记录日志、修改请求数据等
  6. }
  7. public void OnActionExecuted(ActionExecutedContext context)
  8. {
  9.     Console.WriteLine("CustomActionFilter: OnActionExecuted - Action has executed.");
  10.     // 可以在这里添加自定义逻辑,比如记录日志、修改响应数据等
  11.    var stopwatch = context.HttpContext.Items["start"] as Stopwatch;
  12.     if (stopwatch != null)
  13.     {
  14.         stopwatch.Stop();
  15.         Console.WriteLine($"Action executed in {stopwatch.ElapsedMilliseconds} ms");
  16.     }
  17.     else
  18.     {
  19.         Console.WriteLine("CustomActionFilter: Stopwatch not found in HttpContext.");
  20.     }
  21. }
复制代码
控制器的代码:
  1.   [HttpGet("ActionFilter"),CustomActionFilter]
  2.   public IActionResult ActionFilter()
  3.   {
  4.       // 直接返回一个简单的字符串响应
  5.       // 在 Action 中打印日志,用于判断 Action 是否被执行
  6.       Console.WriteLine(">>> Action Executed: Generating new server time...");
  7.       // 返回一个每次都会变化的内容
  8.       var result = new
  9.       {
  10.           message = "This is a response from the server with Action Filter.",
  11.           serverTime = DateTime.Now.ToString("o") // "o" 格式包含毫秒,便于观察
  12.       };
  13.       return Ok(result);
  14.   }
复制代码
会显示执行Action前后的时间。
异步版的代码:
  1. public class CustomAsyncActionFilterAttribute : Attribute, IAsyncActionFilter
  2. {
  3.     public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
  4.   {
  5.       var sw = Stopwatch.StartNew();
  6.       var executed = await next();
  7.       sw.Stop();
  8.       Console.WriteLine($"Async action {context.ActionDescriptor.DisplayName} took {sw.ElapsedMilliseconds}ms" );
  9.   }
  10. }
复制代码
控制器的代码:
  1. [HttpGet("AsyncActionFilter"), CustomAsyncActionFilter]
  2. public async Task<IActionResult> ActionAsyncFilter()
  3. {
  4.      // 直接返回一个简单的字符串响应
  5.      // 在 Action 中打印日志,用于判断 Action 是否被执行
  6.      Console.WriteLine(">>> Async Action Executed: Generating new server time...");
  7.      // 模拟一个异步操作,例如数据库查询或调用外部 API
  8.      await Task.Delay(100); // 暂停 100 毫秒
  9.      var result = new
  10.      {
  11.          message = "This is a fresh ASYNC response from the server with Action Filter.",
  12.          serverTime = DateTime.Now.ToString("o")
  13.      };
  14.      return Ok(result);
  15. }
复制代码
匿名Filter函数

系统支持的AllowAnoymous只能在后续的授权的Filter中去使用,对于我们自己扩展过的Filter,allowAnonymous不能直接使用。
支持Anonymous的匿名---在扩展Filter的时候,也要实现AllowAnonymou匿名。

  • 扩展一个CustomAllowAnonymousAttribute
  • CustomAllowAnonymousAttribute专门用来做匿名的
  • 升级扩展的Filter,让Filter来支持CustomAllowAnonymousAttribute匿名。
  • 在扩展的Filter的内部进行判断,是否标记了这个特性,否则自定义的Filter不生效。
ExceptionFilter


  • 接口:IExceptionFilter/IAsyncExceptionFilter
  • 执行时机:当控制器创建、模型绑定、过滤器或Action方法执行过程中抛出未被处理的异常时被触发。
  • 核心作用:提供一个集中的地方来捕获和处理应用程序中的异常,避免将原始的错误堆栈信息暴露给客户端。
  • 常见用途:

    • 全局异常处理:捕获所有未处理的异常。
    • 记录异常日志:将异常信息详细地记录到日志系统中。
    • 返回统一的错误响应:向客户端返回一个标准化的、友好的错误信息(例如,{"error":An internal server error occurred.}),而不是一个HTML错误页面或堆栈跟踪。

  • 一句话总结:"安全网和事故报告员",专门处理意外情况。
    同步版代码:
    filter的代码:
  1. public class CustomExceptionFilterAttribute:Attribute,IExceptionFilter
  2. {
  3.      public void OnException(ExceptionContext context)
  4.      {
  5.          Console.WriteLine($"CustomExceptionFilter: An exception occurred in action {context.ActionDescriptor.DisplayName}.");
  6.          Console.WriteLine($"Exception Message: {context.Exception.Message}");
  7.          context.ExceptionHandled = true; // 标记异常已处理,防止进一步传播
  8.      }
  9. }
复制代码
控制器的代码
  1. [HttpGet("ExceptionFilter"), CustomExceptionFilter]
  2. public IActionResult ExceptionFilter()
  3. {
  4.      Console.WriteLine(">>> Action Executed: About to throw an exception...");
  5.      // 故意抛出一个异常,触发异常过滤器
  6.      throw new InvalidOperationException("This is a test exception thrown from the action.");
  7. }
复制代码
异步版代码
Filter代码:
  1. public class CustomAsyncExceptionAttribute:Attribute, IAsyncExceptionFilter
  2. {
  3.     public async Task OnExceptionAsync(ExceptionContext context)
  4.     {
  5.         await Task.Run(() =>
  6.         {
  7.             Console.WriteLine($"CustomAsyncExceptionFilter: An exception occurred in action {context.ActionDescriptor.DisplayName}.");
  8.             Console.WriteLine($"Exception Message: {context.Exception.Message}");
  9.             context.ExceptionHandled = true; // 标记异常已处理,防止进一步传播
  10.         });
  11.     }
  12. }
复制代码
控制器的代码
  1. [HttpGet("AsyncExceptionFilter"), CustomAsyncExceptionFilter]
  2. public async Task<IActionResult> AsyncExceptionFilter()
  3. {
  4.      Console.WriteLine(">>> Async Action Executed: About to throw an exception...");
  5.      // 故意抛出一个异常,触发异常过滤器
  6.      await Task.Run(() =>
  7.      {
  8.          throw new InvalidOperationException("This is a test exception thrown from the async action.");
  9.      });
  10.      return Ok(); // 这行代码实际上不会被执行到
  11. }   
复制代码
ResultFilter


  • 接口: IResultFilter / IAsyncResultFilter
  • 执行时机: 仅当 Action 方法成功执行并返回一个 ActionResult 之后,在结果被写入响应体的前后运行。如果 Action 抛出异常或被其他过滤器短路,它不会执行。
  • 核心作用: 对 Action 的执行结果进行最后的加工和处理。
  • 常见用途:

    • 统一响应格式包装: 将所有成功的 API 响应数据包装在一个标准的结构中,例如 { "success": true, "data": ... }。
    • 修改响应头: 根据 Action 的结果动态地添加或修改响应头。
    • 格式化响应数据: 例如,对所有返回的 DateTime 类型进行统一的格式化。
    • 一句话总结: “响应的化妆师”,负责在结果展示给客户前进行最后的包装和美化。
      同步版代码:
      filter的代码:

  1.   public class CustomResultFilterAttribute:Attribute, IResultFilter
  2.   {
  3.       public void OnResultExecuting(ResultExecutingContext context)
  4.       {
  5.           Console.WriteLine("CustomResultFilter: OnResultExecuting - Result is about to be executed.");
  6.           // 可以在这里添加自定义逻辑,比如记录日志、修改响应数据等
  7.       }
  8.       public void OnResultExecuted(ResultExecutedContext context)
  9.       {
  10.           Console.WriteLine("CustomResultFilter: OnResultExecuted - Result has been executed.");
  11.           // 可以在这里添加自定义逻辑,比如记录日志、修改响应数据等
  12.       }
  13.   }
复制代码
控制器的代码
  1. [HttpGet("ResultFilter"), CustomResultFilter]
  2. public IActionResult ResultFilter()
  3. {
  4.     Console.WriteLine(
  5.         ">>> Action Executed: Generating new server time..."
  6.     );
  7.     var result = new
  8.     {
  9.         message = "This is a response from the server with Result Filter.",
  10.         serverTime = DateTime.Now.ToString("o") // "o" 格式包含毫秒,便于观察
  11.     };
  12.     return Ok(result);
  13. }
复制代码
异步版的代码
Filter的代码
  1. public class CustomAsyncResultFilterAttribute: Attribute, IAsyncResultFilter
  2. {
  3.     public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
  4.     {
  5.         Console.WriteLine("CustomAsyncResultFilter: OnResultExecutionAsync - Before result execution.");
  6.         // 可以在这里添加自定义逻辑,比如记录日志、修改响应数据等
  7.         // 调用下一个过滤器或结果执行
  8.         await next();
  9.         Console.WriteLine("CustomAsyncResultFilter: OnResultExecutionAsync - After result execution.");
  10.         // 可以在这里添加自定义逻辑,比如记录日志、修改响应数据等
  11.     }
  12. }
复制代码
控制器的代码
  1. [HttpGet("AsyncResultFilter"),CustomAsyncResultFilter]
  2. public async Task<IActionResult> AsyncResultFilter()
  3. {
  4.      Console.WriteLine(">>> Async Action Executed: Generating new server time...");
  5.      await Task.Delay(100); // 暂停 100 毫秒
  6.      var result = new
  7.      {
  8.          message = "This is a fresh ASYNC response from the server with Result Filter.",
  9.          serverTime = DateTime.Now.ToString("o")
  10.      };
  11.      return Ok(result);
  12. }
复制代码
IAlwaysRunResultFilter

IAlwaysRunResultFilter (和它的异步版本 IAsyncAlwaysRunResultFilter) 是一个非常有用的 Result Filter 接口,它的核心作用正如其名:一个保证总是会运行的 Result Filter。
“无论管道是否被前置的 Filter(如 Authorization, Resource, Action Filter)短路,只要一个 IActionResult 准备被执行,我这个 Filter 就必须运行。”
它忽略了短路信号,确保对最终要发往客户端的任何 Result 对象都能进行处理。
Authorization Filter


  • 接口: IAuthorizationFilter / IAsyncAuthorizationFilter
  • 执行时机: 最早运行。在请求管道中位于所有其他过滤器之前。
  • 核心作用: 决定当前用户是否有权限访问请求的资源。它的职责非常单一和明确:要么通过,要么拒绝。
  • 常见用途:

    • 身份验证检查: 确认用户是否已登录。
    • 角色或策略检查: 检查用户是否属于某个角色(如 "Admin")或满足某个策略(如 "Over18")。
    • API 密钥验证: 检查请求头中是否包含有效的 API 密钥。
    • IP 白名单/黑名单: 检查请求的来源 IP 是否被允许。
    • 一句话总结: “看门人”,决定你是否能进门。
      创建需要验证的Model类

  1. public class UserModel
  2. {
  3.      public int Id { get; set; }
  4.      [Required(ErrorMessage ="用户名为必填项")]
  5.      [Display(Name ="用户名")]
  6.      public string Username { get; set; }
  7.      [Required(ErrorMessage ="密码为必填项")]
  8.      [DataType(DataType.Password)]
  9.      [Display(Name ="密码")]
  10.      public string Password { get; set; }
  11.      // 为了授权填加的字段
  12.      //
  13.      public  string Role { get; set; }
  14.      public  string  Department { get; set; }
  15.      public  DateTime BirthDate { get; set; }
  16.      public  bool  IsEmailVerified { get; set; }
  17.      public  string Email { get; set; }
  18. }
复制代码
填加数字验证码的服务
接口代码 ICaptchaService.cs
  1.   public interface ICaptchaService
  2.   {
  3.       (string text, byte[] imageBytes) GenerateCaptcha();
  4.   }
复制代码
实现数字验证的代码:
要安装SixLabors.ImageSharp和SixLabors.ImageSharp.Drawing的nuget包
  1. public class CaptchaService:ICaptchaService
  2. {
  3.     private const int ImageWidth = 150;
  4.     private const int ImageHeight = 50;
  5.     public (string text, byte[] imageBytes) GenerateCaptcha()
  6.     {
  7.         string captchaText = GenerateRandomText(4);
  8.         using (var image = new Image<Rgba32>(ImageWidth, ImageHeight))
  9.         {
  10.             // Draw the captcha text on the image
  11.             var font = SystemFonts.CreateFont("Arial", 32, FontStyle.Bold);
  12.             image.Mutate(ctx => ctx.Fill(Color.White));
  13.             var random = new Random();
  14.             for (int i = 0; i < 10; i++)
  15.             {
  16.                 var startPoint = new PointF(random.Next(0, ImageWidth), random.Next(0, ImageHeight));
  17.                 var endPoint = new PointF(random.Next(0, ImageWidth), random.Next(0, ImageHeight));
  18.                 var color = Color.FromRgb((byte)random.Next(150, 256), (byte)random.Next(150, 256), (byte)random.Next(150, 256));
  19.                 image.Mutate(ctx => ctx.DrawLine(color, 1, startPoint, endPoint));
  20.             }
  21.             for (int i = 0; i < captchaText.Length; i++)
  22.             {
  23.                 char character = captchaText[i];
  24.                 var location = new PointF(10 + i * 35, 5);
  25.                 var color = Color.FromRgb((byte)random.Next(0, 150), (byte)random.Next(0, 150), (byte)random.Next(0, 150));
  26.                 image.Mutate(ctx => ctx.DrawText(character.ToString(), font, color, location));
  27.             }
  28.             // 5. 将图片保存到内存流
  29.             using (var ms = new MemoryStream())
  30.             {
  31.                 image.SaveAsPng(ms);
  32.                 return (captchaText, ms.ToArray());
  33.             }
  34.         }
  35.     }
  36.     private string GenerateRandomText(int length)
  37.     {
  38.         const string chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
  39.         var random = new Random();
  40.         var sb = new StringBuilder();
  41.         for (int i = 0; i < length; i++)
  42.         {
  43.             sb.Append(chars[random.Next(chars.Length)]);
  44.         }
  45.         return sb.ToString();
  46.     }
  47. }
复制代码
视图的代码
  1. @* filepath: Views/Login/Login.cshtml *@
  2. @model UserModel
  3. @{
  4.     ViewData["Title"] = "登录";
  5. }
  6. <h1>@ViewData["Title"]</h1>
  7.    
  8.         <form asp-action="Login" method="post">
  9.             @* 用于显示摘要错误,如“用户名或密码无效” *@
  10.             
  11.             
  12.                 <label asp-for="Username" ></label>
  13.                 <input asp-for="Username"  autocomplete="username" />
  14.                
  15.             
  16.             
  17.                 <label asp-for="Password" ></label>
  18.                 <input asp-for="Password"  autocomplete="current-password" />
  19.                
  20.             
  21.             @* --- 新增字段开始 --- *@
  22.             
  23.                 <label asp-for="Email" ></label>
  24.                 <input asp-for="Email"  autocomplete="email" />
  25.                
  26.             
  27.             
  28.                 <label asp-for="Role" ></label>
  29.                 <input asp-for="Role"  />
  30.                
  31.             
  32.             
  33.                 <label asp-for="Department" ></label>
  34.                 <input asp-for="Department"  />
  35.                
  36.             
  37.             
  38.                 <label asp-for="BirthDate" ></label>
  39.                 @* Tag Helper 会自动为 DateTime 生成 type="date" *@
  40.                 <input asp-for="BirthDate"  />
  41.                
  42.             
  43.             
  44.                 @* 对于布尔值,使用 form-check 样式 *@
  45.                 <input  asp-for="IsEmailVerified" />
  46.                 <label  asp-for="IsEmailVerified"></label>
  47.                
  48.             
  49.             @* --- 新增字段结束 --- *@
  50.             
  51.                 <label for="captchaCode" >验证码</label>
  52.                
  53.                     @*
  54.                         关键点 1:
  55.                         使用手动的 name="captchaCode" 和 id="captchaCode"。
  56.                         name="captchaCode" 必须与 Controller Action 的参数名匹配。
  57.                     *@
  58.                     <input name="captchaCode" id="captchaCode"   autocomplete="off" />
  59.                     
  60.                         @*
  61.                             关键点 2:
  62.                             使用 <img> 标签,其 src 指向返回图片流的 Action。
  63.                         *@
  64.                         <img id="captcha-image" src="https://www.cnblogs.com/@Url.Action("GetCaptchaImage", "Login")"
  65.                              alt="Captcha"  title="点击刷新验证码" />
  66.                     
  67.                
  68.                 @*
  69.                     关键点 3:
  70.                     这个验证标签可以保留,它会显示 ModelState 中键为 "CaptchaCode" 的错误。
  71.                 *@
  72.                 @Html.ValidationMessage("CaptchaCode", "", new { @class = "text-danger" })
  73.             
  74.             
  75.                 <input type="submit" value="登录"  />
  76.             
  77.         </form>
  78.    
  79. @section Scripts {
  80.     @{
  81.         await Html.RenderPartialAsync("_ValidationScriptsPartial");
  82.     }
  83.    
  84. }
复制代码
填加退出的视图代码
  1. @* 只在用户登录后显示登出按钮 *@
  2. @if (User.Identity.IsAuthenticated)
  3. {
  4.     <form asp-controller="Login" asp-action="Logout" method="post" >
  5.         <button type="submit" >登出</button>
  6.     </form>
  7. }
复制代码
控制器的代码
LOGIN的控制器
  1.   public class LoginController : Controller
  2.   {
  3.       private readonly ILogger<LoginController> _logger;
  4.       private readonly ICaptchaService _captchaService;
  5.       private const string CaptchaSessionKey = "CaptchaCode";
  6.       public LoginController(ILogger<LoginController> logger, ICaptchaService captchaService)
  7.       {
  8.           _logger = logger;
  9.           _captchaService = captchaService;
  10.       }
  11.       [HttpGet]
  12.       public IActionResult Login()
  13.       {
  14.           return View();
  15.       }
  16.       [HttpGet]
  17.       public IActionResult GetCaptchaImage()
  18.       {
  19.           var (text, imageBytes) = _captchaService.GenerateCaptcha();
  20.           HttpContext.Session.SetString(CaptchaSessionKey, text);
  21.           _logger.LogInformation("Generated Captcha Image with text: {Captcha}", text);
  22.           return File(imageBytes, "image/png");
  23.       }
  24.       [HttpPost]
  25.       [ValidateAntiForgeryToken]
  26.       public async Task<IActionResult> Login(UserModel model, string captchaCode)
  27.       {
  28.           if (string.IsNullOrEmpty(captchaCode))
  29.           {
  30.               ModelState.AddModelError("CaptchaCode", "验证码为必填项。");
  31.           }
  32.           if (!ModelState.IsValid)
  33.           {
  34.               return View(model);
  35.           }
  36.           var sessionCaptcha = HttpContext.Session.GetString(CaptchaSessionKey);
  37.           if (sessionCaptcha == null || !sessionCaptcha.Equals(captchaCode, StringComparison.OrdinalIgnoreCase))
  38.           {
  39.               ModelState.AddModelError("CaptchaCode", "验证码不正确。");
  40.               return View(model);
  41.           }
  42.           if (model.Username.Equals("admin", StringComparison.OrdinalIgnoreCase) && model.Password == "123456")
  43.           {
  44.               var claims = new List<Claim>
  45.               {
  46.                   new Claim(ClaimTypes.NameIdentifier, model.Id.ToString()),
  47.                   new Claim(ClaimTypes.Name, model.Username),
  48.                   new Claim(ClaimTypes.Role, model.Role),
  49.                   new Claim("Department", model.Department),
  50.                   new Claim("BirthDate", model.BirthDate.ToString("yyyy-MM-dd")),
  51.                   new Claim("IsEmailVerified", model.IsEmailVerified.ToString()),
  52.                   new Claim(ClaimTypes.Email, model.Email)
  53.               };
  54.               var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
  55.               await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity));
  56.               _logger.LogInformation("User {UserName} logged in successfully.", model.Username);
  57.               return RedirectToAction("MyProfile", "User");
  58.           }
  59.           ModelState.AddModelError(string.Empty, "用户名或密码无效。");
  60.           return View(model);
  61.       }
  62.       [HttpPost] // 推荐使用 Post 防止 CSRF 攻击
  63.       [ValidateAntiForgeryToken]
  64.       public async Task<IActionResult> Logout()
  65.       {
  66.           // 关键代码:让身份验证 Cookie 失效
  67.           await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
  68.           _logger.LogInformation("User logged out.");
  69.           // 登出后,通常重定向到主页或登录页
  70.           return RedirectToAction("Index", "Home");
  71.       }
复制代码
在Program.cs中
注册服务:
  1. // 注册验证码服务为单例模式
  2. builder.Services.AddSingleton<ICaptchaService, CaptchaService>();
  3. // 添加分布式内存缓存,这是 Session 的基础
  4. builder.Services.AddDistributedMemoryCache();
  5. // 添加 Session 服务并配置
  6. builder.Services.AddSession(options =>
  7. {
  8.     options.IdleTimeout = TimeSpan.FromMinutes(3);
  9.     options.Cookie.HttpOnly = true;
  10.     options.Cookie.IsEssential = true;
  11. });
  12. // 添加 Cookie 认证服务
  13. builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
  14.     .AddCookie(options =>
  15.     {
  16.         options.LoginPath = "/Login/Login";
  17.         options.AccessDeniedPath = "/Home/AccessDenied";
  18.         options.ExpireTimeSpan = TimeSpan.FromMinutes(3);
  19.         options.SlidingExpiration = true;
  20.     });
  21. app.UseAuthorization(); //启动认证。
复制代码
1. [Authorize]常见用法

在登陆成功后就会跳转新的控制器
  1. [Authorize]
  2. [Route("[controller]")]
  3. public class UserController : Controller
  4. {
  5.     [HttpGet("me")]
  6.     public IActionResult GetMyProfile()
  7.     {
  8.         if (User.Identity == null || !User.Identity.IsAuthenticated)
  9.         {
  10.             return Challenge(); // 如果未登录,挑战认证
  11.         }
  12.         // User 是 ControllerBase 提供的属性,代表当前登录的用户
  13.         return Ok(new
  14.         {
  15.             Id = User.FindFirstValue(ClaimTypes.NameIdentifier),
  16.             Username = User.Identity.Name,
  17.             Department = User.FindFirstValue("Department"),
  18.             Role = User.FindFirstValue(ClaimTypes.Role),
  19.             BirthDate = User.FindFirstValue("BirthDate"),
  20.             IsEmailVerified = User.FindFirstValue("IsEmailVerified"),
  21.             Email = User.FindFirstValue(ClaimTypes.Email)
  22.         });
  23.     }
  24.     [HttpGet("all")]
  25.     [Authorize(Roles = "Admin,Manager")]
  26.     public IActionResult GetAllUsers()
  27.     {
  28.         // ... 返回所有用户的逻辑 ...
  29.         return Ok("Returned all users (for Admins/Managers).");
  30.     }
  31.     [AllowAnonymous] // 允许匿名访问,覆盖了控制器级别的 [Authorize]
  32.     [HttpGet("public-announcement")]
  33.     public IActionResult GetPublicAnnouncement()
  34.     {
  35.         return Ok("This is a public announcement.");
  36.     }
  37. }
复制代码
这个代码就显示只有登陆是指定的角色才能访问ALL方法和有认证才能访问ME方法.
2. 常用 Policy 定义写法与场景

在Program.cs(或Startup.cs)中配置策略
  1. // 添加 Cookie 认证服务
  2. builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
  3.     .AddCookie(options =>
  4.     {
  5.         options.LoginPath = "/Login/Login";
  6.         options.AccessDeniedPath = "/Home/AccessDenied";
  7.         options.ExpireTimeSpan = TimeSpan.FromMinutes(3);
  8.         options.SlidingExpiration = true;
  9.     });
  10. builder.Services.AddAuthorization(options =>
  11. {
  12.     // 场景1: 基于角色 (等同于 [Authorize(Roles="Admin")])
  13.     options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin"));
  14.     // 场景2: 基于 Claim 的存在与否
  15.     // 要求用户必须有 "department" 这个 Claim,值是什么无所谓
  16.     options.AddPolicy("HasDepartment", policy => policy.RequireClaim("Department"));
  17.     // 场景3: 基于 Claim 的具体值
  18.     // 要求用户的 "email_verified" Claim 的值必须是 "True"
  19.     options.AddPolicy("EmailVerified", policy => policy.RequireClaim("IsEmailVerified", "True"));
  20.     // 场景4: 组合条件
  21.     // 要求用户必须是 "Manager" 角色,并且属于 "Sales" 或 "HR" 部门
  22.     options.AddPolicy("SalesOrHrManager", policy => policy
  23.         .RequireRole("Manager")
  24.         .RequireClaim("department", "Sales", "HR"));
  25.     // 场景5: 自定义条件 (将在下一节实现)
  26.     // 要求用户必须年满18岁
  27.     options.AddPolicy("Over18", policy =>
  28.         policy.AddRequirements(new MinimumAgeRequirement(18)));
  29. });
复制代码
控制器的代码
  1.   [Authorize]
  2.   [Route("[controller]")]
  3.   public class ReportsController : Controller
  4.   {
  5.       // 必须是 Admin
  6.       [HttpGet("financial")]
  7.       [Authorize(Policy = "AdminOnly")]
  8.       public IActionResult GetFinancialReport() => Ok("Financial Report");
  9.       // 必须是销售部或人力资源部的经理
  10.       [HttpGet("performance")]
  11.       [Authorize(Policy = "SalesOrHrManager")]
  12.       public IActionResult GetPerformanceReport() => Ok("Team Performance Report");
  13.       // 必须邮箱已验证
  14.       [HttpGet("confidential-docs")]
  15.       [Authorize(Policy = "EmailVerified")]
  16.       public IActionResult GetConfidentialDocs() => Ok("Confidential Documents");
  17.   }
复制代码
3.  自定义 Requirement + Handler

添加需求,要求大于18岁才能访问
a. 定义 Requirement
它只是一个数据容器,标记需要满足什么条件。
  1. public class MinimumAgeRequirement:IAuthorizationRequirement
  2. {
  3.     public int MinimumAge { get; }
  4.     public MinimumAgeRequirement(int minimumAge)
  5.     {
  6.         MinimumAge = minimumAge;
  7.     }
  8. }
复制代码
b. 定义 Handler
这是真正的逻辑“回调”点,在这里检查用户是否满足 Requirement
  1. public class MinimumAgeHandler:AuthorizationHandler<Requirement.MinimumAgeRequirement>
  2. {
  3.     protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, Requirement.MinimumAgeRequirement requirement)
  4.     {
  5.         // 从当前用户的 Claims 中查找 "birthdate"
  6.         var birthDateClaim = context.User.FindFirst(c => c.Type == "BirthDate");
  7.         if (birthDateClaim == null)
  8.         {
  9.             // 没有找到 birthdate claim,无法判断,直接返回
  10.             return Task.CompletedTask;
  11.         }
  12.         if (DateTime.TryParse(birthDateClaim.Value, out var birthDate))
  13.         {
  14.             var age = DateTime.Today.Year - birthDate.Year;
  15.             // 如果生日还没到,年龄减一
  16.             if (birthDate.Date > DateTime.Today.AddYears(-age))
  17.             {
  18.                 age--;
  19.             }
  20.             // 如果年龄满足要求
  21.             if (age >= requirement.MinimumAge)
  22.             {
  23.                 // 调用 Succeed,表示此 Requirement 已通过
  24.                 context.Succeed(requirement);
  25.             }
  26.         }
  27.         return Task.CompletedTask;
  28.     }
复制代码
c. 注册 Handler
//添加新的policy
  1.    // 场景5: 自定义条件 (将在下一节实现)
  2.    // 要求用户必须年满18岁
  3.    options.AddPolicy("Over18", policy =>
  4.        policy.AddRequirements(new MinimumAgeRequirement(18)));
复制代码
在Program.cs中注册,以便系统能找到它。
  1. builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
复制代码
添加控制器的代码
  1. // 必须年满18岁
  2. [HttpGet("adult-content")]
  3. [Authorize(Policy = "Over18")]
  4. public IActionResult GetAdultContent() => Ok("Adult Content");
复制代码
4. 资源级授权 (Resource-based)

这个场景非常适合“用户只能修改自己的信息,但管理员可以修改任何人的信息”。
a. 定义 Requirement和Operations
Operation:
  1.     public static class MyUserProfileOperations
  2.     {
  3.         public static readonly OperationAuthorizationRequirement Read =
  4.         new() { Name = nameof(Read) };
  5.         public static readonly OperationAuthorizationRequirement Update =
  6.             new() { Name = nameof(Update) };
  7.     }
复制代码
Requirement:
  1. public class MyUserProfileRequirement : IAuthorizationRequirement
  2. {
  3.      public string Name { get; set; }
  4.      public MyUserProfileRequirement(string name)
  5.      {
  6.          Name = name;
  7.      }
  8. }
复制代码
b. 定义 Handler
Handler 会接收到要操作的资源实例 (UserModel)。
  1. protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MyUserProfileRequirement requirement, UserModel resource)
  2. {
  3.      var loggedInUserId = context.User.FindFirstValue(ClaimTypes.NameIdentifier);
  4.      if (loggedInUserId == null)
  5.      {
  6.          return Task.CompletedTask;
  7.      }
  8.      // 如果是管理员,允许任何操作
  9.      if (context.User.IsInRole("Admin"))
  10.      {
  11.          context.Succeed(requirement);
  12.          return Task.CompletedTask;
  13.      }
  14.      // 如果是用户本人,允许读取和更新自己的信息
  15.      if (resource.Id.ToString() == loggedInUserId)
  16.      {
  17.          if (requirement.Name == MyUserProfileOperations.Read.Name ||
  18.              requirement.Name == MyUserProfileOperations.Update.Name)
  19.          {
  20.              context.Succeed(requirement);
  21.          }
  22.      }
  23.      return Task.CompletedTask;
  24. }
复制代码
c. 注册 Handler
  1. builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
复制代码
d. 在控制器中调用
你需要注入IAuthorizationService来手动触发授权检查。
  1. public class UsersController : ControllerBase
  2. {
  3.     private readonly IAuthorizationService _authorizationService;
  4.     private readonly IUserRepository _userRepository; // 假设有一个仓储来获取用户
  5.     public UsersController(IAuthorizationService authorizationService, IUserRepository userRepository)
  6.     {
  7.         _authorizationService = authorizationService;
  8.         _userRepository = userRepository;
  9.     }
  10.     // ... 其他 Action ...
  11.     // GET: api/Users/5
  12.     [HttpGet("{id}")]
  13.     public async Task<IActionResult> GetUserById(int id)
  14.     {
  15.         var userToView = _userRepository.FindById(id);
  16.         if (userToView == null) return NotFound();
  17.         // 检查当前登录用户是否有权限读取目标用户信息
  18.         var authorizationResult = await _authorizationService
  19.             .AuthorizeAsync(User, userToView, UserProfileOperations.Read);
  20.         if (!authorizationResult.Succeeded)
  21.         {
  22.             // 可以返回 Forbid() (403) 或 Challenge() (401)
  23.             return Forbid();
  24.         }
  25.         return Ok(userToView); // 授权成功,返回用户信息
  26.     }
  27. }
复制代码
5. 动态策略提供器 (高级可选)

假设你想创建形如[Authorize(Policy = "IsInDepartment:Sales")]的动态策略,而不想预先定义所有部门。
a. 实现 IAuthorizationPolicyProvider
  1. using Microsoft.AspNetCore.Authorization;
  2. using Microsoft.Extensions.Options;
  3. public class DepartmentPolicyProvider : IAuthorizationPolicyProvider
  4. {
  5.     private const string POLICY_PREFIX = "IsInDepartment:";
  6.     private readonly DefaultAuthorizationPolicyProvider _fallbackProvider;
  7.     public DepartmentPolicyProvider(IOptions options)
  8.     {
  9.         _fallbackProvider = new DefaultAuthorizationPolicyProvider(options);
  10.     }
  11.     public Task GetDefaultPolicyAsync() => _fallbackProvider.GetDefaultPolicyAsync();
  12.     public Task GetFallbackPolicyAsync() => _fallbackProvider.GetFallbackPolicyAsync();
  13.     public Task GetPolicyAsync(string policyName)
  14.     {
  15.         if (policyName.StartsWith(POLICY_PREFIX, StringComparison.OrdinalIgnoreCase))
  16.         {
  17.             var department = policyName.Substring(POLICY_PREFIX.Length);
  18.             var policy = new AuthorizationPolicyBuilder();
  19.             policy.RequireClaim("department", department);
  20.             return Task.FromResult(policy.Build());
  21.         }
  22.         return _fallbackProvider.GetPolicyAsync(policyName);
  23.     }
  24. }
复制代码
b. 注册提供器 (替换默认的)
  1. builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
复制代码
c. 使用
  1. [Authorize(Policy = "IsInDepartment:IT")]
  2. [ApiController]
  3. [Route("api/it-assets")]
  4. public class ItAssetsController : ControllerBase
  5. {
  6.     [HttpGet]
  7.     public IActionResult GetItAssets() => Ok("List of servers and laptops.");
  8. }
复制代码
6.自定义未授权/拒绝返回 (401/403)

如果你不希望默认跳转到登录页,而是返回 JSON,可以配置认证处理器事件。
  1. builder.Services.AddAuthentication("Cookies")
  2.     .AddCookie("Cookies", options =>
  3.     {
  4.         // ...
  5.         options.Events.OnRedirectToLogin = context =>
  6.         {
  7.             context.Response.StatusCode = StatusCodes.Status401Unauthorized;
  8.             context.Response.ContentType = "application/json";
  9.             var json = System.Text.Json.JsonSerializer.Serialize(new { message = "用户未登录或认证已过期。" });
  10.             return context.Response.WriteAsync(json);
  11.         };
  12.         options.Events.OnRedirectToAccessDenied = context =>
  13.         {
  14.             context.Response.StatusCode = StatusCodes.Status403Forbidden;
  15.             context.Response.ContentType = "application/json";
  16.             var json = System.Text.Json.JsonSerializer.Serialize(new { message = "权限不足,无法访问此资源。" });
  17.             return context.Response.WriteAsync(json);
  18.         };
  19.     });
复制代码
过滤器的作用和意义:优雅地解决问题
过滤器允许您将这些横切关注点从业务逻辑中抽离出来,放到独立的、可重用的类中。ASP.NET Core 的请求处理管道会在合适的时机自动调用这些过滤器。
这带来的核心意义是:
关注点分离 (Separation of Concerns):
控制器 (Controller) 只负责业务流程的编排。
服务 (Service) 只负责核心的业务逻辑实现。
过滤器 (Filter) 只负责处理通用的横切关注点。
各司其职,代码结构变得极其清晰。
代码重用和可维护性:
同一个过滤器(例如日志过滤器)可以轻松地应用到整个应用程序、某个控制器或单个 Action 上。
修改一个功能(如异常处理逻辑),只需要修改对应的过滤器类即可,所有地方都会生效。
声明式编程 (Declarative Programming):
您可以通过在控制器或 Action 上添加一个特性 (Attribute)(如 [Authorize] 或 [TypeFilter(typeof(MyFilter))])来“声明”它需要某种行为,而不需要关心这个行为是如何实现的。这让代码的意图更加明显。
关于Filter的作用范围:

  • 标记在Action上,仅对当前的Action函数有效。
  • 标记在控制器上,对于当前控制器下的所有的Action有效。
  • 全局进行标记,对于整个项目的所有函数都生效。
  1. builder.Services.AddControllers(option => option.Filters.Add<CustomResourceAsyncFilterAttribute>());
复制代码
来源:豆瓜网用户自行投稿发布,如果侵权,请联系站长删除

相关推荐

您需要登录后才可以回帖 登录 | 立即注册