• 基于.NetCore3.1系列3-认证授权方案之授权初识


    原文:https://www.cnblogs.com/i3yuan/p/13198355.html


    1.前言

    回顾认证授权方案之JwtBearer认证

    授权

    在上一篇中,我们通过JwtBearer的方式认证,了解在认证时,都是基于Claim的,因此我们可以通过用户令牌获取到用户的Claims,在授权过程中对这些Claims进行验证,从而来判断是否具有获取或执行目标资源操作的权限。本章就来介绍一下 ASP.NET Core 的授权系统的简单使用。

    2.说明

    授权与身份认证是相互独立,但是,授权却需要一种身份验证机制,因此,身份验证可以为当前用户创建一个或多个标识,是确定用户真实身份的过程。而授权是根据标识确定用户可执行的操作的过程,其本质就是具有某种特性的用户会有权限访问某个资源或者执行某个操作。例如:一个拥有管理员身份的用户有创建人员、删除人员、编辑人员和删除人员的操作权限,而一个非管理身份的用户仅有读取自己信息的权限。

    这时候,你可能会问,究竟怎样特性的用户可以被授权访问某个资源或执行某个操作。由此我们引出了授权策略的方式,可以根据用户拥有的角色,也可以根据用户的职位,部门甚至是性别,年龄等等特性进行授权。

    通过建立授权策略方式,检验认证的用户所携带的身份声明(ClaimsPrincipal对象)与授权策略是否一致,从而确定用户可否执行操作。

    授权

    3.授权

    3.1. 基于角色

    3.1.1 添加角色

    将角色赋予某个控制器或控制器内的操作,指定当前用户必须是其角色才能访问请求资源。

    可以使用Authorize属性的Roles特性指定所请求资源的角色。

    例如:

    • 分配了“admin”角色用户进行访问操作
    [Authorize(Roles ="admin")]
    public class WeatherForecastController : ControllerBase
    {
    
    }
    
    • 以逗号分隔角色名来允行多个角色访问操作
    [Authorize(Roles ="admin,user")]
    public class WeatherForecastController : ControllerBase
    { 
    
    
    }
    

    其中只要满足admmin或者user其一就可以进行访问。

    • 同时满足指定的多个角色进行的访问操作
    [Authorize(Roles = "admin")]
    [Authorize(Roles = "user")]
    public class WeatherForecastController : ControllerBase
    { 
    }
    

    3.1.2 添加策略的角色

    可以创建策略的方式进行访问控制,在配置授权服务中添加注册授权服务策略。

    在Startup.cs文件中,通过ConfigureServices()配置服务,创建一个允许具有admin角色的用户才能进行访问的策略

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            //添加授权角色策略
            services.AddAuthorization(options =>
            {
                options.AddPolicy("BaseRole", options => options.RequireRole("admin"));
            });
            //或者指定多个允许的角色
            //services.AddAuthorization(options =>
            // {
            //    options.AddPolicy("MoreBaseRole", options => options.RequireRole("admin","user"));
            // });
        }
    

    在控制器方法使用特性Policy的属性进行策略应用

        [Authorize(Policy = "BaseRole")]
        public class WeatherForecastController : ControllerBase
        {
        
        }
    

    3.2. 基于声明

    3.2.1添加声明

    对当前用户必须拥有的声明,并将声明赋予某个控制器或控制器内的操作,因此,指定声明必须持有对应的值才能访问请求资源。

    声明要求基于策略,所以必须进行构建一个表示声明要求的策略,才能进行授权。

    最简单的类型声明是将判断声明是否存在,而不检查值。

    可以创建策略的方式进行访问控制,在配置授权服务中添加注册授权服务策略。

    在Startup.cs文件中,通过ConfigureServices()配置服务,创建一个允许具有声明的用户才能进行访问的策略

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            //添加基于声明的授权
            services.AddAuthorization(options =>
            {
                options.AddPolicy("BaseClaims", options => options.RequireClaim("name"));
            });
        }
    

    BaseClaims声明策略会检查name当前标识是否存在声明。

    在控制器方法使用特性Policy的属性进行策略应用

        [Authorize(Policy = "BaseClaims")]
        public class WeatherForecastController : ControllerBase
        {
        
        }
    

    但是,大多时候,我们需要声明包含值,只有指定允许值的列表,才能授权成功。所以,可以添加指定值。

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            //添加基于声明的授权,指定允许值列表。
            services.AddAuthorization(options =>
            {
                options.AddPolicy("BaseClaims", options => options.RequireClaim("name","i3yuan"));
    
            });
        }
    

    3.3 基于策略

    上面介绍的基于角色和基于声明的授权,都使用了要求、要求处理程序和预配置的策略。这些在构建上提供了便捷,但是最终都是生成授权策略。ASP.NET Core,设计了另一种灵活的授权方式,一种更丰富的可重复使用的授权结构,基于策略的授权,同时这也是授权的核心。

    这节会先讲一下授权策略的应用,在下一节中,会对授权策略的核心进行一步步的详解。
    

    在上面我们简单的介绍了基于策略的角色授权,但是这种方式无非基于角色或者声明多一些。

    因此,这里我们基于自定义策略授权的方式,实现授权。

    自定义授权,就要我们自己写策略提供器,自己根据不同的参数来生成不同的策略,重新实现策略的方式。策略要求由以下两种元素组成:仅保留数据的要求类,以及对用户验证数据的授权处理程序。创建自定义要求,还可以进一步表达特定策略。

    3.3.1. 定义权限策略PermissionRequirement

    定义一个权限策略,这个策略并包含一些属性。

    public class PermissionRequirement: IAuthorizationRequirement
    {
        public string _permissionName { get; }
    
        public PermissionRequirement(string PermissionName)
        {
            _permissionName = PermissionName;
        }
    }
    

    3.3.2. 再定义一个策略处理类

    public class PermissionRequirementHandler : AuthorizationHandler<PermissionRequirement>
    {
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
        {
            var role = context.User.FindFirst(c => c.Type == ClaimTypes.Role);
            if (role != null)
            {
                var roleValue = role.Value;
                if (roleValue==requirement._permissionName)
                {
                    context.Succeed(requirement);
                }
            }
            return Task.CompletedTask;
    

    授权处理程序读取与角色用户关联的声明,并检查自定义的角色,如果角色匹则成功,否则无法返回成功。

    这里的自定义声明是写固定了,但是也可以通过数据库或外部服务的方式进行运行查询获取用户相关角色信息相对应的判断条件,从而在处理程序中进行判断处理。
    

    授权处理程序调用方法 Succeed,同时传递当前要求,以通知此要求已成功得到验证。如果没有传递要求,处理程序无需执行任何操作,可以直接返回内容。不过,如果处理程序要确定是否不符合要求(无论其他处理程序是否已成功验证同一要求),将会对授权上下文对象调用方法 Fail

    3.3.3. 下面展示了如何将自定义要求添加到策略

    (请注意,由于这是自定义要求,因此没有扩展方法,而必须继续处理策略对象的整个 Requirements 集合):

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            //基于自定义策略授权
            services.AddAuthorization(options =>
            {
                options.AddPolicy("customizePermisson",
                  policy => policy
                    .Requirements
                    .Add(new PermissionRequirement("admin")));
            });
            //此外,还需要在 IAuthorizationHandler 类型的范围内向 DI 系统注册新的处理程序:
            services.AddScoped<IAuthorizationHandler, PermissionRequirementHandler>();
            // 如前所述,要求可包含多个处理程序。如果为授权层的同一要求向 DI 系统注册多个处理程序,有一个成功就足够了。
    
        }
    

    3.3.4. 应用自定义的策略的特性

    指定当前用户必须是应用对控制器或控制器内的操作,如

       [Authorize(Policy = "customizePermisson")]
        public class WeatherForecastController : ControllerBase
        { 
        }
    

    4.场景

    在上一篇认证授权方案之JwtBearer认证中,我们已经实现了获取token的方式,这一次,我们实现一个以基于角色场景为例的认证授权。

    在原来生成token的方式中,添加多一个声明角色的Claim,如下:

    new Claim(JwtClaimTypes.Role,"admin")

        [HttpGet]
        public IActionResult GetToken()
        {
            try
            {
                //定义发行人issuer
                string iss = "JWTBearer.Auth";
                //定义受众人audience
                string aud = "api.auth";
                //定义许多种的声明Claim,信息存储部分,Claims的实体一般包含用户和一些元数据
                IEnumerable<Claim> claims = new Claim[]
                {
                    new Claim(JwtClaimTypes.Id,"1"),
                    new Claim(JwtClaimTypes.Name,"i3yuan"),
                    new Claim(JwtClaimTypes.Role,"admin"),
                };
                //notBefore  生效时间
                // long nbf =new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds();
                var nbf = DateTime.UtcNow;
                //expires   //过期时间
                // long Exp = new DateTimeOffset(DateTime.Now.AddSeconds(1000)).ToUnixTimeSeconds();
                var Exp = DateTime.UtcNow.AddSeconds(1000);
                //signingCredentials  签名凭证
                string sign = "q2xiARx$4x3TKqBJ"; //SecurityKey 的长度必须 大于等于 16个字符
                var secret = Encoding.UTF8.GetBytes(sign);
                var key = new SymmetricSecurityKey(secret);
                var signcreds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
                //String issuer = default(String), String audience = default(String), IEnumerable<Claim> claims = null, Nullable<DateTime> notBefore = default(Nullable<DateTime>), Nullable<DateTime> expires = default(Nullable<DateTime>), SigningCredentials signingCredentials = null
                var jwt = new JwtSecurityToken(issuer: iss, audience: aud, claims:claims,notBefore:nbf,expires:Exp, signingCredentials: signcreds);
                var JwtHander = new JwtSecurityTokenHandler();
                var token = JwtHander.WriteToken(jwt);
                return Ok(new
                {
                    access_token = token,
                    token_type = "Bearer",
                });
            }
            catch (Exception ex)
            {
                throw;
            }
        }
    

    对控制器或控制器内的操作,指定当前用户必须是其角色才能访问请求资源,如WeatherForecastController.cs

    [ApiController]
    [Route("[controller]")]
    [Authorize(Roles ="admin")]
    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;
    
        public WeatherForecastController(ILogger<WeatherForecastController> logger)
        {
            _logger = logger;
        }
    
        [HttpGet]
        public IEnumerable<WeatherForecast> Get()
        {
            var rng = new Random();
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray();
        }
    }
    

    5.运行

    5.1. 获取token

    分别获取role为admin和role为user的情况下颁发的token,只有在角色为admin的情况下才能授权通过。

    5.2. 授权资源接口访问

    在role为admin的情况下

    授权

    授权

    在role为user的情况下

    授权

    授权

    由上可知,只有在角色为admin的情况下,才能访问目标资源进行操作。

    6、用例

    为我们提供了多个方法由预定义的IAuthorizationRequirement类型来创建并将其添加到Requirements集合中。

    3.1 应用

    实例应用如下:在ConfigureServices中配置服务中

            public void ConfigureServices(IServiceCollection services)
            {
                services.AddControllers();
    
                var combindPolicy = new AuthorizationPolicyBuilder().RequireClaim("role").Build();
                services.AddAuthorization(options =>
                {
                    //DenyAnonymousAuthorizationRequirement
                    options.AddPolicy("DenyAnonyUser", policy => policy.RequireAuthenticatedUser());
                    //NameAuthorizationRequirement
                    options.AddPolicy("NameAuth", policy => policy.RequireUserName("艾三元"));
                    //ClaimsAuthorizationRequirement
                    options.AddPolicy("ClaimsAuth", policy => policy.RequireClaim("role","admin"));
                    //RolesAuthorizationRequirement
                    options.AddPolicy("RolesAuth", policy => policy.RequireRole("admin","user"));
                    //AssertionRequirement
                    options.AddPolicy("AssertAuth", policy => policy.RequireAssertion(c=>c.User.HasClaim(o=>o.Type=="role")));
                    //同样可可用直接调用Combind方法,策略AuthorizationPolicy
                    options.AddPolicy("CombindAuth", policy => policy.Combine(combindPolicy));
                });
    }
    

    以上,分别实现了框架中默认实现的几种IAuthorizationRequirement实现类型在实际中的应用,通过不同授权要求实现的策略方式,同时也可以将上面多种方式合并成一个对象,进行调用使用。

    3.2 拓展

    当然了,除了自带了这几种默认实现方式之外,我们也可以通过自定义Requirement来满足我们的需求。

    这个在上一节初识授权的时候,已经提到了自定义授权这一块,所以在这里再看一次。

    定义一个权限策略PermissionRequirement,这个策略并包含一些属性。

    public class PermissionRequirement: IAuthorizationRequirement
    {
        public string _permissionName { get; }
    
        public PermissionRequirement(string PermissionName)
        {
            _permissionName = PermissionName;
        }
    }
    

    再定义一个策略处理类

    public class PermissionRequirementHandler : AuthorizationHandler<PermissionRequirement>
    {
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
        {
            var role = context.User.FindFirst(c => c.Type == ClaimTypes.Role);
            if (role != null)
            {
                var roleValue = role.Value;
                if (roleValue==requirement._permissionName)
                {
                    context.Succeed(requirement);
                }
            }
            return Task.CompletedTask;
    

    配置使用

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            //基于自定义策略授权
            services.AddAuthorization(options =>
            {
                options.AddPolicy("customizePermisson",
                  policy => policy
                    .Requirements
                    .Add(new PermissionRequirement("admin")));
            });
            //此外,还需要在 IAuthorizationHandler 类型的范围内向 DI 系统注册新的处理程序:
            services.AddScoped<IAuthorizationHandler, PermissionRequirementHandler>();
            // 如前所述,要求可包含多个处理程序。如果为授权层的同一要求向 DI 系统注册多个处理程序,有一个成功就足够了。
        }
    

    特别说明

    上述使用的处理程序是一对一的关系,当声明要求满足条件的时候,则任务授权成功, 授权成功后, context.Succeed 将通过满足要求作为其唯一参数调用。

    但是授权策略中也包含一对多的要求关系,它们属于 & 的关系,只用全部验证通过,才能最终授权成功。但是在有些场景下,我们可能希望一个授权策略可以适用多种情况,比如,我们进入公司时需要出示员工卡才可以被授权进入,但是如果我们忘了带员工卡,可以去申请一个临时卡,同样可以授权成功。

    这里贴一个官方文档的写法:
    
    public class BuildingEntryRequirement : IAuthorizationRequirement
    {
    }
    public class BadgeEntryHandler : AuthorizationHandler<BuildingEntryRequirement>
    {
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                       BuildingEntryRequirement requirement)
        {
            if (context.User.HasClaim(c => c.Type == "BadgeId" &&
                                           c.Issuer == "http://microsoftsecurity"))
            {
                context.Succeed(requirement);
            }
    
            //TODO: Use the following if targeting a version of
            //.NET Framework older than 4.6:
            //      return Task.FromResult(0);
            return Task.CompletedTask;
        }
    }
    public class TemporaryStickerHandler : AuthorizationHandler<BuildingEntryRequirement>
    {
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, 
                                                       BuildingEntryRequirement requirement)
        {
            if (context.User.HasClaim(c => c.Type == "TemporaryBadgeId" &&
                                           c.Issuer == "https://microsoftsecurity"))
            {
                // We'd also check the expiration date on the sticker.
                context.Succeed(requirement);
            }
    
            //TODO: Use the following if targeting a version of
            //.NET Framework older than 4.6:
            //      return Task.FromResult(0);
            return Task.CompletedTask;
        }
    }
    
    

    我们定义了两个Handler,但是想让它们得到执行,还需要将其注册到DI系统中:

    services.AddSingleton<IAuthorizationHandler, BadgeEntryHandler>();
    services.AddSingleton<IAuthorizationHandler, TemporaryStickerHandler>();
    

    确保两个处理程序都已注册。 如果某个处理程序在某一策略评估后使用context.succeed()来成功 BuildingEntryRequirement ,则策略评估将成功。但是当我们调用context.Fail()方法后会将授权结构设置失败,那样的话,最后的结果都是会授权失败的。所以正常情况下。我们都是只设置标记context.succeed()

    7.总结

    1. 从上一篇的认证到这一篇的授权阶段,简单的介绍了Asp.net Core的认证授权系统,对授权有了初步的认识以及使用,对授权进行划分为两种,一种是基于角色的授权,但随着角色的增加会对处理授权产生限制,不适合表达复杂的授权逻辑。另一种是基于策略的身份验证,策略包含一系列基于声明的要求,以及基于可从 HTTP 上下文或外部源注入的其他任何信息的自定义逻辑。这些要求各自与一个或多个处理程序相关联,这些处理程序负责要求的实际计算。
    2. 可以发现,asp.net core提供的授权策略是一个非常强大丰富且灵活的认证授权方案,能够满足大部分的授权场景。
    3. 如果有不对的或不理解的地方,希望大家可以多多指正,提出问题,一起讨论,不断学习,共同进步。
    4. 因此,在后续的篇章中,会继续探索授权系统,对授权策略的核心进行一步步的详解。
    5. 本示例源码地址

    参考文献文档

  • 相关阅读:
    JVM 关于对象分配在堆、栈、TLAB的理解
    分布式唯一 ID 生成方案有哪些?
    JVM 栈帧之操作数栈与局部变量表 转
    C# TreeHelper帮助类
    Java:Top K问题的解法
    C#单例模式
    C#分组方式比较
    Vue实现登录
    git使用总结
    js实现无色彩球
  • 原文地址:https://www.cnblogs.com/springsnow/p/13841554.html
Copyright © 2020-2023  润新知