ASP.NETCore-中间件Middleware(八)_授权中间件-授权方案
一、序言
1、什么是授权
授权是指判断用户可执行的操作的过程。 例如,允许管理用户创建文档库和添加、编辑以及删除文档。 使用该库的非管理用户仅被授予阅读文档的权限。
2、认证与授权之间的联系
“身份验证”是确定用户身份的过程,“授权”是确定用户是否有权访问资源的过程,授权与身份验证相互独立; 但是,授权需要一种身份验证机制。 身份验证是确定用户标识的一个过程。 身份验证可为当前用户创建一个或多个标识。
二、微软官方提供的的授权方案(部分;WepApi常用部分)
以下授权方案都需要在Program.cs中启用认证+授权
app.UseAuthentication(); // 认证
app.UseAuthorization(); // 授权
1、简单授权
(1)控制器或操作添加[Authorize]标识
ASP.NET Core 中的授权由AuthorizeAttribute 及其各种参数控制。 在其最基本的形式中,将[Authorize]
属性应用于控制器、操作或 Razor 页面,将对该组件的访问权限限制为经过身份验证的用户。
你还可以使用AllowAnonymous
属性来允许未经身份验证(经过身份验证、未经过身份验证、匿名)的用户访问单个操作。
#region 控制器
[Authorize] // AuthorizeAttribute 属性应用于控制器
public class AccountController : Controller
{
[AllowAnonymous] // AllowAnonymous 属性使“允许未经身份验证的用户访问Login操作”
public ActionResult Login()
{
}
public ActionResult Logout()
{
}
}
#endregion 控制器
#region 操作
public class AccountController : Controller
{
public ActionResult Login()
{
}
[Authorize] // 如果要将授权应用于操作而不是控制器,请将 AuthorizeAttribute 属性应用于操作本身
public ActionResult Logout()
{
}
}
#endregion 操作
(2)关于 AllowAnonymous 的说明
[AllowAnonymous]
绕过所有授权语句。 如果将[AllowAnonymous]
和任何[Authorize]
属性结合使用,系统将忽略[Authorize]
属性。 例如,如果在控制器级别应用[AllowAnonymous]
,则忽略同一控制器 (或其中的任何操作上的任何[Authorize]
属性) 。
2、基于角色的授权(简单示例;示例为“固定控制器或者操作特性”的形式;角色权限的灵活配置性查)
(1)将角色服务添加到 Identity
一个用户可以绑定多个角色,一个角色可以绑定多个用户。
builder.Services.AddDefaultIdentity<IdentityUser>( ... ) // 注册基于用户的Identity授权服务IdentityUser
.AddRoles<IdentityRole>() // 注册基于角色的Identity授权服务IdentityRole
...
(2)添加角色检查标识(使用特性标识)
① 基础使用:
[Authorize(Roles = "Administrator")] // Administrator角色的用户才能调用该控制器中的内容;多个角色可以指定为逗号分隔的列表,如:[Authorize(Roles = "HRManager,Finance")]
public class AdministrationController : Controller
{
public IActionResult Index() =>
Content("Administrator");
}
② 限制用户既是 角色“PowerUser”又是角色“ControlPanelUser”:
// 控制器-限制用户既是 角色“PowerUser”又是角色“ControlPanelUser”
[Authorize(Roles = "PowerUser")]
[Authorize(Roles = "ControlPanelUser")]
public class ControlPanelController : Controller
{
public IActionResult Index() => Content("PowerUser && ControlPanelUser");
}
③ 限制某个操作只有用户是角色“Administrator”才能访问;其他操作用户角色可以是“Administrator”或“PowerUser”
[Authorize(Roles = "Administrator, PowerUser")]
public class ControlAllPanelController : Controller
{
public IActionResult SetTime() => Content("Administrator || PowerUser");
// 操作-限制该操作只有用户是角色“Administrator”才能访问;其他操作用户角色可以是“Administrator”或“PowerUser”
[Authorize(Roles = "Administrator")]
public IActionResult ShutDown() => Content("Administrator only");
}
3、基于声明的授权(策略的一种)
代码中的声明指定当前用户必须拥有的声明,以及声明必须持有的值(可选)才能访问所请求的资源。最简单的声明策略类型会查找是否存在声明,且不会对值进行检查。
(1)注册策略
builder.Services.AddAuthorization(options => // 配置Authorization
{
options.AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber")); // 配置策略Policy;添加声明检查-当前写法为只查找是否存在EmployeeNumber声明
options.AddPolicy("Founders", policy => policy.RequireClaim("EmployeeNumber", "1", "2", "3", "4", "5")); // 多值的格式
});
...
app.UseAuthorization(); // 启用Authorization
(2)添加角色检查标识
[Authorize(Policy = "EmployeeOnly")]
public class VacationController : Controller
{
public IActionResult Index()
{
return View();
}
public ActionResult VacationBalance()
{
return View();
}
[AllowAnonymous] // 免授权
public ActionResult VacationPolicy()
{
return View();
}
}
(3)补充-多策略格式:
[Authorize(Policy = "EmployeeOnly")]
public class SalaryController : Controller
{
public IActionResult Index()
{
return View();
}
public IActionResult Payslip()
{
return View();
}
[Authorize(Policy = "HumanResources")]
public IActionResult UpdateSalary() // EmployeeOnly+HumanResources 授权都通过时才可用
{
return View();
}
}
[Authorize(Policy = "EmployeeOnly")]
[Authorize(Policy = "HumanResources")]
public class SalaryModel : PageModel // 同时满足EmployeeOnly、HumanResources 授权才可用
{
public ContentResult OnGetPayStub()
{
return Content("OnGetPayStub");
}
public ContentResult OnGetSalary()
{
return Content("OnGetSalary");
}
}
更多写法见上面的:二-1-(2)添加角色检查标识(使用特性标识)
4、基于策略的授权
- 基于角色的授权:基于角色的授权和基于声明的授权均使用预配置的形式,他们都是策略授权的一种实现;修改这些策略需要重新编译程序。
- 基于策略的授权:允许管理员在应用程序中定义精细的授权策略,并修改这些策略而无需重新编译或重启应用程序。
如果我们需要自定义一种策略授权,可参考下面的示例。
(1)自定义策略授权-游戏登录接口_用户年龄限制在13岁
① 注册授权
#region 自定义授权策略-游戏登录接口_用户年龄限制在13岁
builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>(); // 一般使用AddScoped达到不需要重新启动的目的
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AtLeast13", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(13)));
});
#endregion 自定义授权策略-游戏登录接口_用户年龄限制在13岁
② 创建“自定义授权策略-游戏登录接口_用户年龄限制在13岁”的标记 MinimumAgeRequirement
using Microsoft.AspNetCore.Authorization;
namespace fly_chat1_net7.Middlewares.UserAuthorization_Diy
{
/// <summary>
/// “自定义授权策略-游戏登录接口_用户年龄限制在13岁”的标记
/// </summary>
public class MinimumAgeRequirement : IAuthorizationRequirement // 实现空的标记接口 IAuthorizationRequirement
{
public MinimumAgeRequirement(int minimumAge) => MinimumAge = minimumAge;
public int MinimumAge { get; }
}
}
③ 重写 AuthorizationHandler 的允许授权方法 HandleRequirementAsync
using Microsoft.AspNetCore.Authorization;
using System.Security.Claims;
namespace fly_chat1_net7.Middlewares.UserAuthorization_Diy
{
/// <summary>
/// “自定义授权策略-游戏登录接口_用户年龄限制在13岁”的 授权处理程序
/// 重写HandleRequirementAsync允许授权方法
/// </summary>
public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
/// <summary>
/// 根据特定要求和资源决定是否允许授权
/// </summary>
/// <param name="context">授权上下文</param>
/// <param name="requirement">要评估的要求</param>
/// <returns></returns>
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MinimumAgeRequirement requirement)
{
var dateOfBirthClaim = context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth && c.Issuer == "http://contoso.com"); // 获取用户的出生年月
if (dateOfBirthClaim is null)
{
return Task.CompletedTask; // 忽略-不进行授权
}
// 计算年龄
var dateOfBirth = Convert.ToDateTime(dateOfBirthClaim.Value);
int calculatedAge = DateTime.Today.Year - dateOfBirth.Year;
if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
{
calculatedAge--;
}
if (calculatedAge >= requirement.MinimumAge) // 比较
{
context.Succeed(requirement); // 符合条件的用户进行授权
}
return Task.CompletedTask; // 忽略-不进行授权
}
}
}
④ 添加角色检查标识
[Authorize(Policy = "AtLeast13")]
public class GameLoginController : Controller
{
public ActionResult Login()
{
return View();
}
[AllowAnonymous] // 免授权
public ActionResult Show()
{
return View();
}
}
(2)自定义策略授权-多策略要求实现(接口权限设置,如:只读、编辑、删除)
① 注册授权
// 自定义授权策略-接口权限设置(只读、编辑、删除)
builder.Services.AddSingleton<IAuthorizationHandler, MultiplePermissionsHandler>(); // 一般使用AddScoped达到不需要重新启动的目的
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("ReadPermission", policy => policy.Requirements.Add(new ReadPermission()));
options.AddPolicy("EditPermission", policy => policy.Requirements.Add(new EditPermission()));
options.AddPolicy("DeletePermission", policy => policy.Requirements.Add(new DeletePermission()));
});
② 创建“自定义授权策略-多策略”的标记只读ReadPermission
、编辑EditPermission
、删除DeletePermission
。
using Microsoft.AspNetCore.Authorization;
namespace fly_chat1_net7.Middlewares.UserAuthorization_Diy
{
/// <summary>
/// 只读
/// </summary>
public class ReadPermission: IAuthorizationRequirement // 实现空的标记接口 IAuthorizationRequirement
{
// 实现
}
/// <summary>
/// 编辑
/// </summary>
public class EditPermission : IAuthorizationRequirement // 实现空的标记接口 IAuthorizationRequirement
{
// 实现
}
/// <summary>
/// 删除
/// </summary>
public class DeletePermission : IAuthorizationRequirement // 实现空的标记接口 IAuthorizationRequirement
{
// 实现
}
}
③ 实现IAuthorizationHandler的HandleAsync方法(上个案例为重写 AuthorizationHandler 的允许授权方法 HandleRequirementAsync)
using Microsoft.AspNetCore.Authorization;
using System.Security.Claims;
namespace fly_chat1_net7.Middlewares.UserAuthorization_Diy
{
/// <summary>
/// “自定义授权策略-多权限要求的示例”的 授权处理程序
/// 实现IAuthorizationHandler的HandleAsync方法
/// </summary>
public class MultiplePermissionsHandler : IAuthorizationHandler
{
/// <summary>
/// 实现HandleAsync方法
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public Task HandleAsync(AuthorizationHandlerContext context)
{
var pendingRequirements = context.PendingRequirements.ToList();
foreach (var requirement in pendingRequirements)
{
if (requirement is ReadPermission)
{
if (IsOwner(context.User, context.Resource)
|| IsSponsor(context.User, context.Resource))
{
context.Succeed(requirement);
}
}
else if (requirement is EditPermission || requirement is DeletePermission)
{
if (IsOwner(context.User, context.Resource))
{
context.Succeed(requirement);
}
}
}
return Task.CompletedTask;
}
/// <summary>
/// 判断是否是所有者
/// </summary>
/// <param name="user"></param>
/// <param name="resource"></param>
/// <returns></returns>
private static bool IsOwner(ClaimsPrincipal user, object? resource)
{
// Code omitted for brevity
return true;
}
/// <summary>
/// 判断是否是被许可者
/// </summary>
/// <param name="user"></param>
/// <param name="resource"></param>
/// <returns></returns>
private static bool IsSponsor(ClaimsPrincipal user, object? resource)
{
// Code omitted for brevity
return true;
}
}
}
④ 添加角色检查标识
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
private readonly ILogger<WeatherForecastController> _logger;
TsetService _TsetService;
public WeatherForecastController(ILogger<WeatherForecastController> logger, ISqlSugarClient iSqlSugarClient)
{
_logger = logger;
_TsetService = new TsetService(iSqlSugarClient);
}
[HttpGet(Name = "GetWeatherForecast")]
//[AllowAnonymous]
[Authorize(Policy = "ReadPermission")]
public IEnumerable<WeatherForecast> Get()
{
_TsetService.Insert();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
}).ToArray();
}
[HttpPost(Name = "Update")]
[Authorize(Policy = "EditPermission")]
public IEnumerable<bool> Update()
{
return true;
}
[HttpDelete(Name = "Delete")]
[Authorize(Policy = "DeletePermission")]
public IEnumerable<bool> Delete()
{
return true;
}
}
(3)补充-IAuthorizationHandler及其实现类的处理结果context.Succeed(requirement)
;
一般只需要调用context.Succeed(IAuthorizationRequirement requirement)
传递已成功验证的要求即可;
处理程序通常不需要处理失败,因为多策略要求的处理不能保证所有的策略都成功,所以不能在某条策略处理中就返回失败给前端。若必须使用,可调用context.Fail
,强制结束所有策略处理并返回失败给前端。
(4)补充-使用“lambda表达式格式”注册授权(policy.RequireAssertion)
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("BadgeEntry", policy =>
policy.RequireAssertion( // 使用policy.RequireAssertion进行声明
context => context.User.HasClaim(c =>
(c.Type == "BadgeId" || c.Type == "TemporaryBadgeId")&& c.Issuer == "https://microsoftsecurity")
)
);
});
5、基于资源的授权(新手只需要有这个东西即可,遇到场景再学习使用;比如对文档进行展示、编辑、删除等管理)
(1)注册授权策略
// 基于资源的授权策略-文档管理
builder.Services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationHandler>(); // 自定义的授权策略-文档
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("DocumentEditPolicy", policy =>policy.Requirements.Add(new DocumentAuthorizationRequirement()));
});
(2)创建"基于资源的授权"的标记
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Infrastructure;
namespace fly_chat1_net7.Middlewares.UserAuthorization_Diy
{
/// <summary>
/// "基于资源的授权演示Demo"的标记
/// </summary>
public class DocumentAuthorizationRequirement : IAuthorizationRequirement
{
/// <summary>
/// 操作名称
/// </summary>
public string Name { get; set; } = string.Empty;
}
/// <summary>
/// OperationAuthorizationRequirement 帮助程序类
/// 根据 CRUD 的结果做出决策, (创建、读取、更新、删除) 操作 —对应“操作名称”
/// </summary>
public static class Operations
{
public static OperationAuthorizationRequirement Create =new OperationAuthorizationRequirement { Name = nameof(Create) };
public static OperationAuthorizationRequirement Read =new OperationAuthorizationRequirement { Name = nameof(Read) };
public static OperationAuthorizationRequirement Update =new OperationAuthorizationRequirement { Name = nameof(Update) };
public static OperationAuthorizationRequirement Delete =new OperationAuthorizationRequirement { Name = nameof(Delete) };
}
}
(3)创建"基于资源的授权"的 授权处理程序
using Microsoft.AspNetCore.Authorization;
namespace fly_chat1_net7.Middlewares.UserAuthorization_Diy
{
/// <summary>
/// "基于资源的授权演示Demo"的 授权处理程序
/// 重写HandleRequirementAsync允许授权方法
/// </summary>
public class DocumentAuthorizationHandler : AuthorizationHandler<DocumentAuthorizationRequirement, Document>
{
/// <summary>
/// 根据特定要求和资源决定是否允许授权
/// </summary>
/// <param name="context">授权上下文</param>
/// <param name="requirement">要评估的要求</param>
/// <param name="resource">资源</param>
/// <returns></returns>
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
DocumentAuthorizationRequirement requirement,
Document resource)
{
if (context.User.Identity?.Name == resource.Author &&
requirement.Name == Operations.Read.Name) // 是作者并且具有可查看权限时,则进行授权
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
/// <summary>
/// 文档Model
/// </summary>
public class Document
{
/// <summary>
/// 结果Code
/// </summary>
public int RuseltCode { get; set; }
/// <summary>
/// 结果信息
/// </summary>
public string RuseltMessage { get; set; } = string.Empty;
/// <summary>
/// 文件Id
/// </summary>
public string Id { get; set; } = string.Empty;
/// <summary>
/// 文件对象
/// </summary>
public object FileObj { get; set; } = string.Empty;
/// <summary>
/// 文件名
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// 文件描述
/// </summary>
public string Description { get; set; } = string.Empty;
/// <summary>
/// 文件大小
/// </summary>
public float Size { get; set; }
/// <summary>
/// 作者
/// </summary>
public string Author { get; set; } = string.Empty;
}
}
(4)“文档”操作Controller
using fly_chat1_net7.Middlewares.UserAuthorization_Diy;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
namespace fly_chat1_net7.Controllers.Demo
{
/// <summary>
/// “文档”控制器Controller
/// </summary>
public class MyDocumentController
{
private readonly IAuthorizationService _authorizationService;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="authorizationService">授权处理(参数:接受资源和策略名称+接受资源和要评估的要求列表)</param>
/// <param name="documentRepository"></param>
public MyDocumentController(IAuthorizationService authorizationService)
{
_authorizationService = authorizationService;
}
/// <summary>
/// 通过文档Id获取文件-登录即可查看
/// </summary>
/// <param name="documentId">文档Id</param>
/// <returns></returns>
public async Task<Document> OnGetAsync(string documentId)
{
Document document = null;
//Document document = _documentRepository.Find(documentId); // 查询是否存在
if (document == null)
{
return new Document()
{
Id = documentId,
RuseltCode = -1,
RuseltMessage = "未找到要查询的文档!"
};
}
ClaimsPrincipal user = new ClaimsPrincipal();
var authorizationResult = await _authorizationService.AuthorizeAsync(user, document, Operations.Read);
if (authorizationResult.Succeeded || user.Identity.IsAuthenticated)
{
return document;
}
else
{
return new Document()
{
Id = documentId,
RuseltCode = -1,
RuseltMessage = "请登录后进行查看!"
};
}
}
}
}
三、微软官方提供的其他授权方案相关内容
- 通过授权创建 Web 应用
- Razor Pages 授权约定
- 基于视图的授权
- 按方案限制标识
见:https://learn.microsoft.com/zh-cn/aspnet/core/security/authorization/introduction?view=aspnetcore-6.0
四、“基于角色的授权”的通用扩展方案(基于策略 Policy 的角色检查;提升的角色权限的灵活配置性)
1、“基于 Policy 的角色检查”的基础写法:
(1)配置Authorization(Program.cs):
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("RequireAdministratorRole",
// policy => policy.RequireRole("Administrator"));
policy.RequireRole("Administrator", "PowerUser", "BackupAdministrator"));
});
(2)使用[Authorize]
属性上的Policy属性
应用策略:
[Authorize(Policy = "RequireAdministratorRole")]
public IActionResult Shutdown()
{
return View();
}
2、“基于 Policy 的角色检查”的自定义写法1_不需重新编译但需要重新启动(提升的角色权限的灵活配置性,但修改完权限还需要重新启动程序才能生效)
(1)配置Authorization(Program.cs):
Dictionary<string, List<string>> keyValuePairs = new Dictionary<string, List<string>>(); // “菜单、操作等列表”-“绑定的角色组”(排除“菜单、操作等列表”或“绑定的角色组”为null的项)
List<string> menus = keyValuePairs.Keys.ToList(); // 菜单、操作等列表
List<List<string>> roles = keyValuePairs.Values.ToList(); // 绑定的角色
builder.Services.AddAuthorization(options =>
{
//options.AddPolicy("RequireAdministratorRole",
// // policy => policy.RequireRole("Administrator"));
// policy => policy.RequireRole("Administrator", "PowerUser", "BackupAdministrator"));
foreach (string menu in menus)
{
options.AddPolicy(menu,
policy => policy.RequireRole(keyValuePairs[menu].ToList()));
}
});
(2)使用[Authorize]
属性上的Policy属性
应用策略:
[Authorize(Policy = "RequireAdministratorRole")]
public IActionResult Shutdown()
{
return View();
}
3、“基于 Policy 的角色检查”的自定义写法2_不需重新编译且不需要重新启动(提升的角色权限的灵活配置性)
(1)配置Authorization(Program.cs):
#region “基于 Policy 的角色检查”的自定义写法2_不需重新编译且不需要重新启动
builder.Services.AddScoped<IAuthorizationHandler, MyAuthorizationHandler>();
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("MyWebAPIPolicy", policy => policy.Requirements.Add(new MyAuthorizationRequirement(13)));
});
#endregion “基于 Policy 的角色检查”的自定义写法2_不需重新编译且不需要重新启动
(2)创建“授权处理程序”与“标记”
/// <summary>
/// “基于 Policy 的角色检查”的自定义写法2_不需重新编译且不需要重新启动”的 标记
/// 基于策略的授权
/// </summary>
public class MyAuthorizationRequirement : IAuthorizationRequirement // 实现空的标记接口 IAuthorizationRequirement
{
}
/// <summary>
/// “基于 Policy 的角色检查”的自定义写法2_不需重新编译且不需要重新启动”的 授权处理程序
/// 基于策略的授权
/// 重写HandleRequirementAsync允许授权方法
/// </summary>
public class MyAuthorizationHandler : AuthorizationHandler<MyAuthorizationRequirement>
{
/// <summary>
/// 根据特定要求决定是否允许授权
/// </summary>
/// <param name="context">授权上下文</param>
/// <param name="requirement">要评估的要求</param>
/// <returns></returns>
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MyAuthorizationRequirement requirement)
{
try
{
// 获取过期日期
var expiration = context.User.FindFirst(c => c.Type == ClaimTypes.Expiration
&& c.Issuer == Program.configuration["Jwt:Issuer"]); // 检查token的颁发者
if (expiration is null)
{
return Task.CompletedTask; // 忽略-不进行授权
}
// 校验时间格式
var tryResult = DateTime.TryParse(expiration.ToString(), out DateTime expiresAt);
if (!tryResult)
{
return Task.CompletedTask; // 忽略-不进行授权
}
// 判断是否超期
var utcNow = DateTime.UtcNow.AddMinutes(Convert.ToDouble(Program.configuration["Jwt:ExpireMinutes"]));
if (utcNow > expiresAt)
{
return Task.CompletedTask; // 忽略-不进行授权
}
if (true) // 进行鉴权
{
context.Succeed(requirement); // 符合条件的用户进行授权
}
return Task.CompletedTask; // 忽略-不进行授权
}
catch (Exception ex)
{
// 记录错误
Log.Fatal(ex, "授权处理中间件MyAuthorizationHandler出错!错误信息:" + ex.Message); // Serilog
return Task.CompletedTask; // 忽略-不进行授权
}
}
}
(3)使用[Authorize]
属性上的Policy属性
应用策略:
[Authorize(Policy = "MyWebAPIPolicy")]
public IActionResult Shutdown()
{
return View();
}
五、授权中间件的代替品-授权过滤器Filter
好多作者不喜欢用授权中间件,采用Filter过滤器的方式进行鉴权,这样做一般也是够用的。
示例后补
本文来自博客园,作者:꧁执笔小白꧂,转载请注明原文链接:https://www.cnblogs.com/qq2806933146xiaobai/articles/17492711.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 一文读懂知识蒸馏
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下