一、序言

1、什么是授权

  授权是指判断用户可执行的操作的过程。 例如,允许管理用户创建文档库和添加、编辑以及删除文档。 使用该库的非管理用户仅被授予阅读文档的权限。

2、认证与授权之间的联系

  “身份验证”是确定用户身份的过程,“授权”是确定用户是否有权访问资源的过程,授权与身份验证相互独立; 但是,授权需要一种身份验证机制。 身份验证是确定用户标识的一个过程。 身份验证可为当前用户创建一个或多个标识。

二、微软官方提供的的授权方案(部分;WepApi常用部分)

以下授权方案都需要在Program.cs中启用认证+授权
app.UseAuthentication();  // 认证
  app.UseAuthorization();   // 授权
  • 1.
  • 2.
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 操作
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
(2)关于 AllowAnonymous 的说明

  [AllowAnonymous]绕过所有授权语句。 如果将[AllowAnonymous]和任何[Authorize]属性结合使用,系统将忽略[Authorize]属性。 例如,如果在控制器级别应用[AllowAnonymous],则忽略同一控制器 (或其中的任何操作上的任何[Authorize]属性) 。

2、基于角色的授权(简单示例;示例为“固定控制器或者操作特性”的形式;角色权限的灵活配置性查)
(1)将角色服务添加到 Identity

  一个用户可以绑定多个角色,一个角色可以绑定多个用户。

builder.Services.AddDefaultIdentity<IdentityUser>( ... )   // 注册基于用户的Identity授权服务IdentityUser
      .AddRoles<IdentityRole>()  // 注册基于角色的Identity授权服务IdentityRole
      ...
  • 1.
  • 2.
  • 3.
(2)添加角色检查标识(使用特性标识)

  ① 基础使用:

[Authorize(Roles = "Administrator")]  // Administrator角色的用户才能调用该控制器中的内容;多个角色可以指定为逗号分隔的列表,如:[Authorize(Roles = "HRManager,Finance")]
public class AdministrationController : Controller
{
    public IActionResult Index() =>
        Content("Administrator");
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

  ② 限制用户既是 角色“PowerUser”又是角色“ControlPanelUser”:

// 控制器-限制用户既是 角色“PowerUser”又是角色“ControlPanelUser”
[Authorize(Roles = "PowerUser")]
[Authorize(Roles = "ControlPanelUser")]
public class ControlPanelController : Controller
{
    public IActionResult Index() => Content("PowerUser && ControlPanelUser");
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

  ③ 限制某个操作只有用户是角色“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");
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
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
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
(2)添加角色检查标识
[Authorize(Policy = "EmployeeOnly")]
public class VacationController : Controller
{
    public IActionResult Index()
    {
        return View();
    }

    public ActionResult VacationBalance()
    {
        return View();
    }

    [AllowAnonymous]  // 免授权
    public ActionResult VacationPolicy()
    {
        return View();
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
(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();
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
[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.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

二-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岁
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

  ② 创建“自定义授权策略-游戏登录接口_用户年龄限制在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; }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

  ③ 重写 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;  // 忽略-不进行授权
        }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.

  ④ 添加角色检查标识

[Authorize(Policy = "AtLeast13")]
public class GameLoginController : Controller
{
    public ActionResult Login()
    {
        return View();
    }

    [AllowAnonymous]  // 免授权
    public ActionResult Show()
    {
        return View();
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
(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()));
  });
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

  ② 创建“自定义授权策略-多策略”的标记只读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
    {
        // 实现
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.

  ③ 实现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;
        }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.

  ④ 添加角色检查标识

[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;
        }
    }
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
(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")
      )
    );
});
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
5、基于资源的授权(新手只需要有这个东西即可,遇到场景再学习使用;比如对文档进行展示、编辑、删除等管理)
(1)注册授权策略
// 基于资源的授权策略-文档管理
  builder.Services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationHandler>();  // 自定义的授权策略-文档
  builder.Services.AddAuthorization(options =>
  {
      options.AddPolicy("DocumentEditPolicy", policy =>policy.Requirements.Add(new DocumentAuthorizationRequirement()));
  });
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
(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) };
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
(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;
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
(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 = "请登录后进行查看!"
                };
            }
        }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.

三、微软官方提供的其他授权方案相关内容

  • 通过授权创建 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"));
});
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
(2)使用[Authorize]属性上的Policy属性应用策略:
[Authorize(Policy = "RequireAdministratorRole")]
public IActionResult Shutdown()
{
    return View();
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
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()));

      }
  });
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
(2)使用[Authorize]属性上的Policy属性应用策略:
[Authorize(Policy = "RequireAdministratorRole")]
public IActionResult Shutdown()
{
    return View();
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
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_不需重新编译且不需要重新启动
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
(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;  // 忽略-不进行授权
            }
        }
    }
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
(3)使用[Authorize]属性上的Policy属性应用策略:
[Authorize(Policy = "MyWebAPIPolicy")]
public IActionResult Shutdown()
{
    return View();
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

五、授权中间件的代替品-授权过滤器Filter

  好多作者不喜欢用授权中间件,采用Filter过滤器的方式进行鉴权,这样做一般也是够用的。

  示例后补

作者:꧁执笔小白꧂