修身养性,知行合一

  • 首页
  • 爱码
    • 系统
    • 数据库
    • JavaScript
    • CSharp
    • Python
  • 生活
    • 文化
    • 美食
  • 杂谈
  • 关于
修身养性,知行合一
码字,杂谈
  1. 首页
  2. 爱码
  3. CSharp
  4. ASP.NET
  5. 正文

IdentityServer4深入使用(二)-- 认证与授权(下)

2021年3月22日 3534点热度 0人点赞 0条评论

开始之前

上篇文章已经学习了如何认证,本篇将深入学习如何授权,如果需要继续理解认证的,包括基础认证,JWT 认证,以及如何在 .Net 项目中使用认证的,都可以看 上篇文章。

更多学习内容,可以看我的 .NET 学习之路系列-认证与授权。

授权

授权通常是针对用户可执行的操作。在 .NET 的解决方案中,授权的使用是非常简单的。它通过 AuthorizeAttribute 和其各种参数来控制。

所有的授权都是在认证之后的,如果开启了授权,而没有提供认证方案,则会报错。
下面的代码,都默认已经配置好了 JWT 认证方案。

授权的使用

其最简单的形式就是将 [Authrize] 属性应用于控制器、操作或 Razor 页面。

[Authorize]
public class AccountController : Controller
{
    public ActionResult Login()
    {
    }

    public ActionResult Logout()
    {
    }
}

如果要对操作应用授权而非控制器,则置于操作本身即可:

public class AccountController : Controller
{
   public ActionResult Login()
   {
   }

   [Authorize]
   public ActionResult Logout()
   {
   }
}

对于控制器需要权限,而里面某些应用不需要授权的情况,使用 [AllowAnonymous] 属性进行控制:

[Authorize]
public class AccountController : Controller
{
    [AllowAnonymous]
    public ActionResult Login()
    {
    }

    public ActionResult Logout()
    {
    }
}

AllowAnonymous 属性会跳过所有授权语句,如果它与 Authorize 组合使用,则会忽略 Authorize 属性,并且该效果向下兼容。

基于角色授权

想要使用角色授权,首先要在令牌中添加角色:

具体代码可以看 示例代码

添加角色

var token = new JwtSecurityToken(
issuer: "jeremyjone@qq.com",
audience: "jeremyjone",
expires: DateTime.UtcNow.AddHours(1),
signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256),
claims: new Claim[]
{
    // 角色需要在这里填写
    new Claim(ClaimTypes.Role, "Admin"),
    // 多个角色可以重复写,生成的 JWT 会是一个数组
    new Claim(ClaimTypes.Role, "Super")
});

使用角色进行授权

在 [Authorize] 后面跟上角色参数,即可使用角色授权。

// 只有角色为 Admin 的用户可以使用
[Authorize(Roles = "Admin")]
public IEnumerable<WeatherForecast> Get()
{
}

如果想使用两个或以上的角色,则可以:

// 多个角色使用逗号(,)分开,它们是 “或” 的关系
[Authorize(Roles = "Admin,User")]
public IEnumerable<WeatherForecast> Get()
{
}

// 多个角色分开写是 “且” 的关系
[Authorize(Roles = "Admin")]
[Authorize(Roles = "Super")]
public IEnumerable<WeatherForecast> Get()
{
}

基于策略授权

具体代码可以看 示例代码

上面的角色过于简单,.NET 提供了更加强大的功能--基于策略。

简单来说,就是将上面的 Roles 改为 Policy 即可。

[Authorize(Policy = "AdminAndSuper")]
public IEnumerable<WeatherForecast> Get()
{
}

策略授权使用起来确实方便很多,但是其配置也更复杂。

配置角色策略

在 Startup.ConfigureServices 中添加授权服务:

services.AddAuthorization(options =>
{
    // 配置角色策略
    options.AddPolicy("AdminAndSuper", ploicy => policy.RequireRole("Admin").RequireRole("Super"));
    options.AddPolicy("AdminOrSuper", ploicy => policy.RequireRole("Admin", "Super"));
});

关键通过 RequireRole 方法可以实现匹配角色,多参数为“或”的关系,链式为“且”的关系。

配置声明策略

声明策略通过匹配令牌中的声明(Claim)进行授权。其方式与角色策略的配置方式差不多:

services.AddAuthorization(options =>
{
    // 创建声明的策略
    options.AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNo"));
    options.AddPolicy("Founders", policy =>
        // 给声明添加指定允许值的列表
        policy.RequireClaim("EmployeeNo", "1", "2", "3", "4", "5"));
});

配置自定义策略

上面的方式还是比较简单,有时候需要一些更为复杂的方案,比如是否存在某一个声明属性,或者进行一些更高级的判断等,这时候就需要自定义。

比如实现一个查询是否包含某一声明属性:

services.AddAuthorization(options =>
{
    // 自定义策略
    options.AddPolicy("HasBirthDay",
        // 实现一个简易的自定义策略
        policy => policy.RequireAssertion(context =>
            context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth)));
});
创建自定义策略类

这样还是不方便,则可以使用自定义策略类来处理:

services.AddAuthorization(options =>
{
    // 继承自 IAuthorizationRequirement 接口的策略
    options.AddPolicy("AtLeast18", policy =>
        // 实现一个至少18岁的策略
        policy.Requirements.Add(new MinimumAgeRequirement(18)));
});

创建一个 MinimumAgeRequirement 类:

/// <summary>
/// 最小年龄策略要求类,它需要实现 IAuthorizationRequirement 接口
/// </summary>
public class MinimumAgeRequirement : IAuthorizationRequirement
{
    public MinimumAgeRequirement(int minAge)
    {
        MinAge = minAge;
    }

    public int MinAge { get; }
}

然后创建一个针对我们需要的策略(至少 18 岁)进行处理。创建一个 MinimumAgeHandle 类,它继承自 AuthorizationHandler<TRequirement> 抽象类,TRequirement 是一个泛型,这里应该使用 MinimumAgeRequirement 进行替换。

/// <summary>
/// 最小年龄的处理类,它需要实现 AuthorizationHandler<TRequirement> 抽象类
/// </summary>
public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
        MinimumAgeRequirement requirement)
    {
        if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth))
        {
            return Task.CompletedTask;
        }

        var dateOfBirth = context.User.FindFirstValue(ClaimTypes.DateOfBirth);
        if (string.IsNullOrWhiteSpace(dateOfBirth)) return Task.CompletedTask;

        var birth = Convert.ToDateTime(dateOfBirth);
        var age = DateTime.Today.Year - birth.Year;
        if (birth > DateTime.Today.AddYears(-age)) age--;

        if (age >= requirement.MinAge)
            // Succeed 方法是验证成功的必要语句
            context.Succeed(requirement);

        return Task.CompletedTask;
    }
}

最后需要将操作类在服务中注入:

// 注册自定义策略的处理程序
services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();

然后,我们可以将控制器中的策略改为 [Authorize(Policy = "AtLeast18")] 以查看效果。如果令牌中包含年龄,并且年龄大于 18 岁则会正常进入,反之不会。

在策略中使用参数

上面的例子中,年龄是写死的,这样对控制器应用非常不友好,如果要获取很多不同年龄段的权限,需要配置对应多项的策略,这样就很麻烦。好在 .NET 给我们提供了一个 Provider,让我们可以方便配置。

具体代码可以看 示例代码

基于上例,继续创建一个 MinimumAgeAuthorizeAttribute 类,它继承自 AuthorizeAttribute:

/// <summary>
/// 通过将参数映射到一个字符串,用于检索相应的授权策略。
/// </summary>
internal class MinimumAgeAuthorizeAttribute : AuthorizeAttribute
{
    private const string POLICY_PREFIX = "MinimumAge";
    public MinimumAgeAuthorizeAttribute(int age) => Age = age;

    public int Age
    {
        get => int.TryParse(Policy.Substring(POLICY_PREFIX.Length), out var age) ? age : default;
        set => Policy = $"{POLICY_PREFIX}{value.ToString()}";
    }
}

写完了它,在控制器就可以使用 [MinimumAgeAuthorize(18)] 属性了。

但是还没有对应的处理程序,它并不能真正运行起来。该属性仅仅是收集授权参数为一个特定字符串,我们需要通过程序将该字符串解析为一个年龄数字,然后判断数字是不是符合标准。

创建一个 MinimumAgePolicyProvider 类,它继承自 IAuthorizationPolicyProvider:

/// <summary>
/// 提供最小年龄的授权服务,通过 IAuthorizationPolicyProvider 接口,实现 GetPolicyAsync 方法即可。
/// </summary>
internal class MinimumAgePolicyProvider: IAuthorizationPolicyProvider
{
    private const string POLICY_PREFIX = "MinimumAge";

    public DefaultAuthorizationPolicyProvider FallbackPolicyProvider { get; }

    public MinimumAgePolicyProvider(IOptions<AuthorizationOptions> options)
    {
        // 提供其他授权策略方案,这里使用默认方案
        FallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
    }

    /// <summary>
    /// 获取策略并进行处理
    /// </summary>
    /// <param name="policyName"></param>
    /// <returns></returns>
    public Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
    {
        // 获取策略字符串并解析出年龄
        if (policyName.StartsWith(POLICY_PREFIX, StringComparison.OrdinalIgnoreCase) &&
            int.TryParse(policyName.Substring(POLICY_PREFIX.Length), out var age))
        {
            // 这里其实和 Startup.ConfigureService 中的配置差不多,都是添加一个 Requirement
            var policy = new AuthorizationPolicyBuilder();
            policy.AddRequirements(new MinimumAgeRequirement(age));
            return Task.FromResult(policy.Build());
        }

        //return Task.FromResult<AuthorizationPolicy>(null);
        // 当上述授权流程出现问题(例如获取不到年龄),则使用该备选方案
        return FallbackPolicyProvider.GetPolicyAsync(policyName);
    }

    //public Task<AuthorizationPolicy> GetDefaultPolicyAsync()
    //{
    //    throw new NotImplementedException();
    //}

    //public Task<AuthorizationPolicy> GetFallbackPolicyAsync()
    //{
    //    throw new NotImplementedException();
    //}

    public Task<AuthorizationPolicy> GetDefaultPolicyAsync() => FallbackPolicyProvider.GetDefaultPolicyAsync();

    public Task<AuthorizationPolicy> GetFallbackPolicyAsync() => FallbackPolicyProvider.GetFallbackPolicyAsync();
}

最后,将 Handler 和 Provider 都注册到服务中:

// 注入授权服务
services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
services.AddSingleton<IAuthorizationPolicyProvider, MinimumAgePolicyProvider>();

这样再启动服务器,就可以进行正常而灵活的授权了。

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可
标签: IdentityServer
最后更新:2021年3月24日

jeremyjone

这个人很懒,什么都没留下

打赏 点赞
< 上一篇
下一篇 >

文章评论

取消回复

文章目录
  • 开始之前
  • 授权
    • 授权的使用
    • 基于角色授权
    • 基于策略授权
最新 热点 随机
最新 热点 随机
node-sass 的安装 解决端口被占的问题 vue3 组件 Props 的声明方式 给 div 添加选中状态 请求的取消 rgb 颜色小数兼容问题
若您正以管理员身份运行 Visual Studio Code - Insiders 用户范围的安装,更新功能会被禁用。 你好,世界! rgb 颜色小数兼容问题 windows中自定义图标不能正常显示 .NET Core 的 URL 中文路径编码问题 Windows server 2012 IIS 安装 core 2.2后直接503的解决方案

(っ•̀ω•́)っ✎⁾⁾ 开心每一天

COPYRIGHT © 2021 jeremyjone.com. ALL RIGHTS RESERVED.

THEME KRATOS MADE BY VTROIS

京ICP备19012859号-1

京公网安备 11010802028585号