【ASP.NET Core】用配置文件来设置授权角色

asp,net,core,配置文件,设置,授权,角色 · 浏览次数 : 997

小编点评

**代码解析** 该代码展示了一种简单基于配置的身份验证方案,主要用于一个名为 Demo 的控制器中。 **主要功能:** * 定义了多个授权策略节点名称,如 cust1、cust2 等。 * 使用 ClaimsIdentity 对象存储用户角色信息。 * 在 API 中使用中间件进行身份验证。 * 通过配置文件动态设置角色列表。 **关键代码:** * `CustAuthenticationSchemeDefault` 类定义了一个名为 `SchemeName` 的静态属性,存储授权策略名称。 * `DemoController` 中的 `Backup` 和 `Hello` 方法用于演示身份验证流程。 * `UseRouting` 和 `UseAuthorization` 方法用于配置中间件管道。 * `app.UseRouting()` 和 `app.UseAuthorization()` 设置了路由和授权规则。 **代码结构:** 1. **配置:** * 配置验证架构 (包含授权策略配置)。 * 加载配置文件设置角色列表。 2. **身份验证:** * 通过 URL 中的参数获取用户角色。 * 验证用户角色是否与配置中的策略匹配。 * 如果匹配,存储角色信息在 ClaimsIdentity 中。 3. **中间件:** * 使用中间件管道处理身份验证过程。 * 在验证成功后设置用户角色。 4. **API 路由:** * 使用 `Route` 和 `Controller` 来定义 API 路由。 * 路由到 `DemoController` 的 `Backup` 和 `Hello` 方法。 **优点:** * 简单的配置,易于维护。 * 动态角色设置,可根据需求更新。 * 使用中间件进行身份验证,提升安全性。 **缺点:** * 代码复杂性可能略高,需要考虑性能的影响。 * 策略名称需要事先规划好,不可随意更改。

正文

在开始之前,老周先祝各个次元的伙伴们新春快乐、生活愉快、万事如意。

在上一篇水文中,老周介绍了角色授权的一些内容。本篇咱们来聊一个比较实际的问题——把用于授权的角色名称放到外部配置,不要硬编码,以方便后期修改。

由于要配置的东西比较简单,咱们并不需要存在数据库,而是用 JSON 文件配置就可以了。将授权策略和角色列表关联起来。比如,老周这里有个 authorRoles.json 文件,它的内容如下:

{
  "cust1": {
    "roles": ["admin", "supperuser"]
  },
  "cust2": {
    "roles": ["user", "web", "logger"]
  }
}

其中,cust1、cust2 是策略名称,所以上面就配置了两个授权策略。每个策略下有个 roles 属性,它的值是数组,这个数组用来指定此策略下允许的角色列表。故:cust1 策略下允许admin、supperuser两种角色的用户访问;cust2 策略下允许 user、web、logger 角色的用户访问。

在 WebApplicationBuilder 的配置中,咱们可以单独加载 authorRoles.json 文件中的内容,然后根据配置文件内容动态添加授权策略。

1、先把配置文件中的内容读出来。

// 配置文件名
const string roleConfigFile = "authorRoles.json";
// 单独加载配置
IConfigurationBuilder configBuilder = new ConfigurationBuilder();
// 添加配置源,此处是JSON文件
configBuilder.AddJsonFile(roleConfigFile);
// 生成配置树对象
IConfiguration myconfig = configBuilder.Build();

此时,myconfig 变量中就包含了 authorRoles.json 文件的内容了。

2、动态添加授权策略。

var builder = WebApplication.CreateBuilder(args);

// 根据配置文件的内容来设置授权策略
builder.Services.AddAuthorization(opt =>
{
    foreach (IConfigurationSection cc in myconfig.GetChildren())
    {
        var policyName = cc.Key;
        opt.AddPolicy(policyName, pbd =>
        {
            // 获取子节点
            var roles = cc.GetSection("roles");
            // 取出角色名称列表
            string[]? roleslist = roles.Get<string[]>();
            if (roleslist is not null)
            {
                // 添加角色
                pbd.RequireRole(roleslist);
                // 关联验证架构
                pbd.AddAuthenticationSchemes(CustAuthenticationSchemeDefault.SchemeName);
            }
        });
    }
});

在读配置的时候,GetChildren 方法会返回两个节点:cust1 和 cust2。然后用 GetSection 再读下一层,即 roles。接着用 Get 方法就能把字符串数组类型的角色列表读出来了。

这里关联了一个验证架构(或叫验证方案),这个验证架构是老周自己写的,主要是为了简单。老周这个示例是用 Web API 的形式呈现的,所以,不用 Cookie,而是用一个简单的 Token,调用时附加在 URL 的查询字符串中传递给服务器。

如果你的项目的 Token 只是在自己项目中用,不用遵守通用标准,你完全可以自己生成。生成方式你看着办,比如用随机字节什么的都行。在 Token 中不要带密码等安全信息。毕竟,Token 这种东西你说安全,也不见得多安全,别人只要拿到你的 Token 就可以代替你访问服务器。当然你会说,我把 Token 加密再传输。其实别人盗你的 Token 根本不需要知道明文,人家只要按照正确的传递方式(如 Query String、Cookies 等),把你加密后的 Token 放上去,也可以冒用你身份的。所以,很多开放平台都会分配给你 App Key 和密钥,并且强调你的密钥必须保管好,不能让别人知道。

下面看看老周自己写的验证。

    using Microsoft.AspNetCore.Authentication;
    using Microsoft.AspNetCore.Http;
    using System.Threading.Tasks;

    public class CustAuthenticationHandler : IAuthenticationHandler
    {
#pragma warning disable CS8618
        private HttpContext HttpContext { set; get; }
        private AuthenticationScheme Scheme { get; set; }
#pragma warning restore CS8618

        public Task<AuthenticateResult> AuthenticateAsync()
        {
            // 获取配置的Token
            IConfiguration appconfig = HttpContext.RequestServices.GetRequiredService<IConfiguration>();
            string[]? tks = appconfig.GetSection("custAuthen:tokens").Get<string[]>();
            if (tks != null && tks.Length > 0 && HttpContext.Request.Query.TryGetValue("token", out var reqToken))
            {
                // 看看有没有效
                if (!tks.Any(t => t == reqToken))
                {
                    return Task.FromResult(AuthenticateResult.Fail("未提供有效的Token"));
                }
                // 成功
                var tickit = new AuthenticationTicket(HttpContext.User, Scheme.Name);
                return Task.FromResult(AuthenticateResult.Success(tickit));
            }
            return Task.FromResult(AuthenticateResult.NoResult());
        }

        public Task ChallengeAsync(AuthenticationProperties? properties)
        {
            HttpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
            return Task.CompletedTask;
        }

        public Task ForbidAsync(AuthenticationProperties? properties)
        {
            HttpContext.Response.StatusCode = StatusCodes.Status403Forbidden;
            return Task.CompletedTask;
        }

        public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
        {
            if (context == null) throw new ArgumentNullException("context");
            HttpContext = context;
            Scheme = scheme;
            // 看看验证架构是否一致
            if (!scheme.Name.Equals(CustAuthenticationSchemeDefault.SchemeName, StringComparison.OrdinalIgnoreCase))
            {
                throw new Exception("验证架构不一致");
            }
            return Task.CompletedTask;
        }
    }

    public static class CustAuthenticationSchemeDefault
    {
        public readonly static string SchemeName = "CustToken";
    }

这里老周没有用什么高级算法生成 Token,而是四个字符串(字符串也是随便输入的),表示四个 Token,只要有一个匹配就算是验证成功了。这些 Token 全写在 appsettings.json 里面。

{
  "Logging": {
    ……
    }
  },
  "AllowedHosts": "*",
  "custAuthen": {
    "tokens": [
      "662CV08Y4GHXOP3",
      "BI4C68DLO2HOS0D",
      "7GSEJ0J8F0246K5",
      "O9FG6V974KWO9G8"
    ]
  }
}

所以,访问这四个 Token 的配置路径就是 custAuthen:tokens。

在实现 ForbidAsync 和 ChallengeAsync 方法时,不要调用 HttpContext 的扩展方法 ForbidAsync、ChallengeAsync,因为这些扩展方法内部是通过调用 AuthenticationService 类的 ForbidAsync、ChallengeAsync 方法实现的。最终又会回过头来调用 CustAuthenticationHandler 类的  ChallengeAsync、ForbidAsync 方法。这等于转了一圈,到头来自己调用自己,易造成无限递归。所以这里我只设置一个 Status Code 就好了。

在服务容器上注册一下自定义的验证处理方案。

var builder = WebApplication.CreateBuilder(args);
// 添加验证功能
builder.Services.AddAuthentication(opt =>
{
    // 注册验证架构(方案)
    opt.AddScheme<CustAuthenticationHandler>(CustAuthenticationSchemeDefault.SchemeName, displayName: null);
});

所以,整个应用程序的初始化代码就是这样。

// 配置文件名
const string roleConfigFile = "authorRoles.json";
// 单独加载配置
IConfigurationBuilder configBuilder = new ConfigurationBuilder();
// 添加配置源,此处是JSON文件
configBuilder.AddJsonFile(roleConfigFile);
// 生成配置树对象
IConfiguration myconfig = configBuilder.Build();

var builder = WebApplication.CreateBuilder(args);
// 添加验证功能
builder.Services.AddAuthentication(opt =>
{
    // 注册验证架构(方案)
    opt.AddScheme<CustAuthenticationHandler>(CustAuthenticationSchemeDefault.SchemeName, displayName: null);
});
// 根据配置文件的内容来设置授权策略
builder.Services.AddAuthorization(opt =>
{
    foreach (IConfigurationSection cc in myconfig.GetChildren())
    {
        var policyName = cc.Key;
        opt.AddPolicy(policyName, pbd =>
        {
            // 获取子节点
            var roles = cc.GetSection("roles");
            // 取出角色名称列表
            string[]? roleslist = roles.Get<string[]>();
            if (roleslist is not null)
            {
                // 添加角色
                pbd.RequireRole(roleslist);
                // 关联验证架构
                pbd.AddAuthenticationSchemes(CustAuthenticationSchemeDefault.SchemeName);
            }
        });
    }
});
builder.Services.AddControllers();
var app = builder.Build();

 

之后,是配置中间件管道。为了简单演示,老周没有写用于身份验证的 Web API,而是直接通过 URL 参数来提供当前访问者的角色。实际开发中不能这样做,而应该从数据库中根据用户查询出用户的角色。但此处是为了演示的简单,也是为了延长键盘寿命,就不建数据库了,不然完成这个示例需要一坤年的时间。

不过,咱们知道,授权是用 Claim 来收集信息的,所以,要在授权执行之前收集好信息。我这里用一个中间件,在授权和调用 API 之前执行。

app.Use((context, next) =>
{
    var val = context.Request.Query["role"];
    string? role = val.FirstOrDefault();
    if(role != null)
    {
        ClaimsIdentity id = new(new[]
        {
            new Claim(ClaimTypes.Role, role)
        }/*, CustAuthenticationSchemeDefault.SchemeName*/);
        ClaimsPrincipal p = new(id);
        context.User = p;
    }
    return next();
});

由于 WebApplication 对象默认帮我们调用了 UseRouting 和 UseEndpoints 方法。Web API 在访问时路由的是 MVC 控制器,直接走 End point 路线,会导致咱们上面的 Use 方法设置用户角色的中间件不执行。所以要重新调用 UseRouting 和 UseAuthorization 方法。

app.UseRouting();
app.UseAuthorization();
app.MapControllers();

 

用一个名为 Demo 的控制器来做验证。

[Route("api/[controller]")]
[ApiController]
public class DemoController : ControllerBase
{
    [HttpGet("backup")]
    [Authorize("cust1")]
    public string Backup() => "备份完成";

    [HttpGet("hello/{name}")]
    [Authorize("cust2")]
    public string Hello(string name)
    {
        return $"你好,{name}";
    }
}

cust1、cust2 正是咱们前面配置里的节点名称,即策略名称。例如,调用 Hello 方法使用 cust2 授权策略,它配置的角色为 user、web、loggor。

 

在调用这些 API 时,URL需要携带两个参数:

1、role:用户角色;

2、token:用于验证。

用 http-repl 工具先测试 demo/backup 方法的调用。

 get /api/demo/backup?role=web&token=O9FG6V974KWO9G8

上述调用提供的用户角色为 web,根据前面的配置,web 角色应使用 cust2 策略。但 Backup 方法应用的授权策略是 cust1,因此无权访问,返回 403。

咱们改一下,使用角色为 admin 的用户。

get /api/demo/backup?role=admin&token=O9FG6V974KWO9G8

此时,授权通过,返回 200。

 

 

访问 Hello 方法也一样,授权策略是 cust2,允许的角色是 user、web、logger。

get /api/demo/hello/小红?role=web&token=BI4C68DLO2HOS0D

授权通过,返回 200 状态码。

 

 

用配置文件来设置角色,算是一种简单方案。如果授权需要的角色有变化,只要修改配置文件中的角列表就行。当然,像 cust1、cust2 等策略名称要事先规划好,策略名称不随便改。

有大伙伴会说,干脆连MVC控制器或其方法上应用哪个授权策略也转到配置文件中,岂不美哉!好是好,但不好弄。可以要自己写个授权的 Filter,主要问题是自己写有时候没有官方内置的代码严谨,容易出“八阿哥”。

所以,综合复杂性与灵活性的平衡,在不扩展现有接口的前提下,咱们这个示例是比较好的,至少,咱们可以在配置文件中修改角色列表。

与【ASP.NET Core】用配置文件来设置授权角色相似的内容:

【ASP.NET Core】用配置文件来设置授权角色

在开始之前,老周先祝各个次元的伙伴们新春快乐、生活愉快、万事如意。 在上一篇水文中,老周介绍了角色授权的一些内容。本篇咱们来聊一个比较实际的问题——把用于授权的角色名称放到外部配置,不要硬编码,以方便后期修改。 由于要配置的东西比较简单,咱们并不需要存在数据库,而是用 JSON 文件配置就可以了。将

Asp-Net-Core开发笔记:使用原生的接口限流功能

前言 之前介绍过使用 AspNetCoreRateLimit 组件来实现接口限流 从 .Net7 开始,AspNetCore 开始内置限流组件,当时我们的项目还在 .Net6 所以只能用第三方的 现在都升级到 .Net8 了,当然是得来试试这个原生组件 体验后:配置使用都比较简单,不过功能也没有 A

【ASP.NET Core】MVC控制器的各种自定义:特性化的路由规则

MVC的路由规则配置方式比较多,咱们用得最多的是两种: A、全局规则。就是我们熟悉的”{controller}/{action}“。 app.MapControllerRoute( name: "bug", pattern: "{controller}/{action}" ); app.MapCon

.NET周刊【7月第1期 2024-07-07】

国内文章 学习.NET 8 MiniApis入门 https://www.cnblogs.com/hejiale010426/p/18280441 MiniApis是ASP.NET Core中的轻量级框架,用最少的代码和配置创建HTTP API。其特点包括简洁明了、性能卓越、灵活多变、易于学习使用,

XUnit数据共享与并行测试

引言 在单元或者集成测试的过程中,需要测试的用例非常多,如果测试是一条一条过,那么需要花费不少的时间。从 V2 开始,默认情况下 XUnit 自动配置并行(参考资料),大大提升了测试速度。本文将对 ASP.NET CORE WEBAPI 程序进行集成测试,并探讨 XUnit 的数据共享与测试并行的方

基于EF Core存储的国际化服务

前言 .NET 官方有一个用来管理国际化资源的扩展包Microsoft.Extensions.Localization,ASP.NET Core也用这个来实现国际化功能。但是这个包的翻译数据是使用resx资源文件来管理的,这就意味着无法动态管理。虽然官方有在文档中提供了一些第三方管理方案,但是都不太

推荐十个优秀的ASP.NET Core第三方中间件,你用过几个?

ASP.NET Core 作为一个强大的、跨平台的、高性能的开源框架,为开发者提供了丰富的功能和灵活的扩展性。其中,中间件(Middleware)是 ASP.NET Core 架构中的核心组件之一,它负责处理 HTTP 请求和响应的管道,允许开发者在请求和响应之间插入自定义逻辑。随着 ASP.NET

Asp-Net-Core学习笔记:gRPC快速入门

## 前言 此前,我在做跨语言调用时,用的是 Facebook 的 Thrift,挺轻量的,还不错。 >Thrift是一种接口描述语言和二进制通讯协议,它被用来定义和创建跨语言的服务。它被当作一个远程过程调用(RPC)框架来使用,是由Facebook为“大规模跨语言服务开发”而开发的。它通过一个代码

Asp-Net-Core开发笔记:快速在已有项目中引入EFCore

前言 很多项目一开始选型的时候没有选择EFCore,不过EFCore确实好用,也许由于种种原因后面还是需要用到,这时候引入EFCore也很方便。 本文以 StarBlog 为例,StarBlog 目前使用的 ORM 是 FreeSQL ,引入 EFCore 对我来说最大的好处是支持多个数据库,如果是

【ASP.NET Core】在 Mini-API 中注入服务

经过版本更新,Mini API 的功能逐步完善,早期支持得不太好的 mini API 现在许多特性都可以用了,比如灰常重要的依赖注入。 咱们先来个相当简单的注入测试。来,定义一个服务类,为了偷懒,老周这里就不使用 接口 + 实现类 的方式了。 public class MyService : IDi