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

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

由于要配置的东西比较简单,咱们并不需要存在数据库,而是用 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】用配置文件来设置授权角色的更多相关文章

  1. 使用JWT的ASP.NET CORE令牌身份验证和授权(无Cookie)——第1部分

    原文:使用JWT的ASP.NET CORE令牌身份验证和授权(无Cookie)--第1部分 原文链接:https://www.codeproject.com/Articles/5160941/ASP- ...

  2. 【ASP.NET Core】运行原理(4):授权

    本系列将分析ASP.NET Core运行原理 [ASP.NET Core]运行原理(1):创建WebHost [ASP.NET Core]运行原理(2):启动WebHost [ASP.NET Core ...

  3. asp.net core 将配置文件配置迁移到数据库(一)

    asp.net core 将配置文件配置迁移到数据库(一) Intro asp.net core 配置默认是项目根目录下的 appsettings.json 文件,还有环境变量以及 command l ...

  4. ASP.NET Core MVC请求超时设置解决方案

    设置请求超时解决方案 当进行数据导入时,若导入数据比较大时此时在ASP.NET Core MVC会出现502 bad gateway请求超时情况(目前对于版本1.1有效,2.0未知),此时我们需要在项 ...

  5. 第十五节:Asp.Net Core中的各种过滤器(授权、资源、操作、结果、异常)

    一. 简介 1. 说明 提到过滤器,通常是指请求处理管道中特定阶段之前或之后的代码,可以处理:授权.响应缓存(对请求管道进行短路,以便返回缓存的响应). 防盗链.本地化国际化等,过滤器用于横向处理业务 ...

  6. asp.net core 控制静态文件的授权

    静态文件访问在网站中是一项重要的服务,用于向前端提供可以直接访问的文件,如js,css,文档等,方法是在Startup的Configure中添加UseStaticFiles()管道. 参考:ASP.N ...

  7. ASP.NET Core策略授权和 ABP 授权

    目录 ASP.NET Core 中的策略授权 策略 定义一个 Controller 设定权限 定义策略 存储用户信息 标记访问权限 认证:Token 凭据 颁发登录凭据 自定义授权 IAuthoriz ...

  8. 在ASP.NET Core应用中如何设置和获取与执行环境相关的信息?

    HostingEnvironment是承载应用当前执行环境的描述,它是对所有实现了IHostingEnvironment接口的所有类型以及对应对象的统称.如下面的代码片段所示,一个HostingEnv ...

  9. asp.net Core 中AuthorizationHandler 实现自定义授权

    前言 ASP.NET Core 中 继承的是AuthorizationHandler ,而ASP.NET Framework 中继承的是AuthorizeAttribute. 它们都是用过重写里面的方 ...

  10. ASP.Net Core 2.1+ Cookie 登录授权验证【简单Cookie验证】

    介绍 本文章发布于博客园:https://www.cnblogs.com/fallstar/p/11310749.html 作者:fallstar 本文章适用于:ASP.NET Core 2.1 + ...

随机推荐

  1. etcd实现分布式锁

    转载自:etcd实现分布式锁 当并发的访问共享资源的时候,如果没有加锁的话,无法保证共享资源安全性和正确性.这个时候就需要用到锁 1.需要具备的特性 需要保证互斥访问(分布式环境需要保证不同节点.不同 ...

  2. 教你用canvas打造一个炫酷的碎片切图效果

    前言 今天分享一个炫酷的碎片式切图效果,这个其实在自己的之前的博客上有实现过,本人觉得这个效果还是挺炫酷的,这次还是用我们的canvas来实现,代码量不多,但有些地方还是需要花点时间去理解的,需要点数 ...

  3. 『现学现忘』Git分支 — 40、分支基本操作(一)

    目录 1.创建分支 (1)创建分支 (2)图示理解 2.查看分支列表 3.分支切换 4.查看所有分支的最后一个提交 5.删除分支 1.创建分支 (1)创建分支 Git 是怎么创建新分支的呢? 很简单, ...

  4. XPAND模板语言语法1.0

    XPAND模板语言语法1.0 Xpand模板语言一般写在以.xpt为结尾的文本文件中 ,以"« »" 作为开头和结尾  .Xpand语言主要包括以下几个标签: «IMPORT», ...

  5. redux中间件

    Redux 中间件 什么是中间件? 中间件本质上就是一个函数,Redux允许我们通过中间件的方式,扩展和增强Redux应用程序,增强体现在对action处理能力上,之前的计数器与弹出框案例中.acti ...

  6. 出现The server time zone value ‘�й���׼ʱ��‘ is unrecognized的解决方法

    使用mybatis链接数据库时出现如下错误, The server time zone value '�й���׼ʱ��' is unrecognized or represents more tha ...

  7. VirtualBox 下 CentOS7 静态 IP 的配置 → 多次踩坑总结,蚌埠住了!

    开心一刻 一个消化不良的病人向医生抱怨:我近来很不正常,吃什么拉什么,吃黄瓜拉黄瓜,吃西瓜拉西瓜,怎样才能恢复正常呢? 医生沉默片刻:那你只能吃屎了 环境准备 VirtualBox 6.1 网络连接方 ...

  8. Vue实现离开页面二次确认

    在项目开发中遇到用户编辑内容后未保存推出编辑页面时需要提示用户"当前数据未保存,是否退出",实际开发中利用window.onbeforeunload方法与vue.$on方法在upd ...

  9. springboot使用jira-rest-java-client-api集成jira,自定义对查询board和sprint的支持

    公司内部使用jira作项目管理,我接到新的需求,要在测试报告上获取jira的所有项目,再根据项目获取board看板,再根据看板获取Sprint,最后获取未完成的bug信息.效果如下: 第一次接入jir ...

  10. C++实现真值表

    这一片文章主要是关于真值表,在完成之前我也遇到了许多问题.比如怎么去求解表达式的值,怎么去将每个变量进行赋值,也就是如何 将n个字符进行01全排列. 01全排列真的神奇,01全排列其实就是2^n.他可 ...