本文将引导你运行Util权限管理模块,并对UI按钮和API操作进行访问控制.
Util平台介绍
Util应用框架是一组类库,它们提供了有用的功能.
虽然Util配套代码生成器能够帮助你创建项目基架,但直接使用它们的成本依然高昂.
第一个挡在前面的障碍是权限功能,它是任何业务项目的基石.
为了减轻使用Util应用框架的负担,我们创建了该项目, 名为 Util Platform, 即 Util平台.
Util平台处于起步阶段,目前提供了基于资源和角色的权限模块,可以控制前端菜单和按钮,并能同时控制API的访问.
后续将持续更新,添加更多基础功能.
开源协议: MIT
可以在商业项目随意使用.
欢迎参与贡献
你如果感兴趣,可以参与该项目的开发, 共同创建基础业务功能,为 .NET 生态添砖加瓦.
- Util应用框架交流QQ群(24791014)
Util平台项目介绍
为了满足单体架构 和微服务架构的需求, Util平台分为三套项目.
-
Util.Platform.Single 是Util平台单体架构版本.
-
Util.Platform.Dapr 是Util平台微服务架构版本.
-
Util.Platform.Share 是Util平台的共享库,抽取单体架构版本和微服务架构版本的共享代码,并发布到Nuget供两个版本使用.
Util平台功能列表
系统功能
- 登录
- 退出登录
权限管理模块
- 应用程序管理
- 声明管理
- 资源管理
- 模块资源管理
- 操作资源管理
- 身份资源管理
- Api资源管理
- 用户管理
- 角色管理
- 将用户添加到角色
- 从角色移除用户
- 权限管理
- 授予角色操作权限
- 拒绝角色操作权限
- 授予角色API权限
- 拒绝角色API权限
准备
拉取 Util.Platform.Single 项目代码.
git clone https://gitee.com/util-core/Util.Platform.Single.git
如果已拉取,请更新到最新代码.
运行项目
打开 Util.Platform.Single.sln 解决方案.
数据迁移
设置 Util.Platform.Api 为启动项目.
打开 appsettings.Development.json 开发配置文件.
DatabaseType 配置项用于指定当前使用的数据库类型,可选值为:
- SqlServer
- PgSql
- MySql
Util.Platform.Single 目前支持以上三种数据库,如果需要支持其它数据库,请告知.
检查你使用的数据库连接字符串是否正确.
按 F5 键,启动 Util.Platform.Api 项目.
启动时,会自动运行 EntityFrameworkCore 迁移命令,创建迁移文件目录.
会同时创建三种数据库项目迁移文件目录.
如果没有配置MySql连接字符串,或 MySql 连接失败, 则无法创建MySql迁移文件目录,并会产生一个异常,不用理会它.
查看数据库,可以看到数据库已创建.
使用 Swagger 测试 Web Api 访问控制
启动完成, 弹出 Swagger 页面.
下面选择一个最简单的操作进行测试,执行 Application 的 /api/Application/enabled 操作.
返回消息 code 为 2.
Code 是 Util 应用框架约定的返回结果代码, 2 代表未授权 .
Util.Platform.Api 项目默认开启了API访问控制,需要先进行登录认证.
Util.Platform.Api 项目的 Swagger 已经配置好 OAuth 认证.
点击 Swagger Authorize 按钮.
弹出 Available authorizations 对话框,点击 Authorize 按钮.
进入登录页面,如下所示.
数据迁移默认创建两个用户: admin 和 test .
admin用户是管理员,test是普通用户.
输入用户 test , 密码 test ,使用普通用户登录.
登录成功,跳回 Swagger 页面,点击 Close 按钮关闭对话框.
重新执行 Application 的 /api/Application/enabled 操作.
可以看到,成功返回数据,说明已经授权成功.
那么是不是只要登录,就能操作所有API接口?
下面执行 Application 的 POST /api/Application 操作,它表示创建应用程序.
返回结果代码为 2,表明未授权,无法访问 API,因为没有给 test 用户设置创建应用程序的权限.
运行管理后台UI
进入 Util.Platform.Single\src\Util.Platform.Ui.Identity\ClientApp 目录.
执行命令,还原 npm 并启动 Angular 开发服务器.
.\start.ps1
设置 Util.Platform.Ui.Identity 和 Util.Platform.Api 为启动项目.
按 F5 键启动项目.
弹出 Api Swagger 和 Angular UI 页面.
Angular UI 已经开启了授权路由守卫,未授权则跳转到登录页面.
使用 admin 用户名登录,密码 admin.
登录成功,进入管理后台UI,如下所示.
查看权限管理模块
查看应用程序
应用程序用于支持多个业务子系统的访问控制.
点击左侧 应用程序 菜单项,如下所示.
数据迁移默认创建的应用程序,名为 管理系统.
你可以添加新的应用程序,点击 创建 按钮,打开 创建应用程序 对话框.
应用程序还用于管理 Identity Server 身份服务器客户端信息.
查看声明
声明是登录认证后可以在用户会话中访问的信息项.
声明非常简单,使用表格列表进行编辑.
查看资源
资源是任何需要访问控制的东西.
它是权限管理模块的核心.
点击左侧 资源 菜单项,如下所示.
查看模块资源
模块资源 用于按功能模块进行分层设置, 同时也显示为前端的 菜单.
UI 左侧菜单即是由模块资源配置的.
并不是所有的模块资源都会在菜单上显示,比如资源菜单下的内部导航菜单,也可以在模块资源中配置,但不会在菜单上显示.
点击 创建 按钮,弹出 创建模块 对话框.
父模块是一个下拉树组件,如下所示.
我们顺便看看 Util UI对下拉树绑定这类常见需求提供的支持.
首先查看 html 代码.
打开 Util.Platform.Ui.Identity/Pages/Routes/Identity/Module/Edit.cshtml 文件,如下所示.
Razor TagHelper 标签设置url直接发起web api请求.
这并非 Angular 推荐用法, 而是从之前 EasyUI 保留下来的经验,对于常规项目,这样能大幅提升开发效率.
对于更复杂的场景,你可以使用 Angular 标准玩法,创建独立的服务,使用服务加载数据,并绑定到组件上.
html
<util-tree-select for="ParentId" query-param="queryParam" load-mode="Sync" url="module/tree" default-expand-all="true"
sort="SortId" label-text="identity.module.parentId" control-span="8">
</util-tree-select>
下面来查看 Angular 组件 typescript 脚本代码.
打开 Util.Platform.Ui.Identity/ClientApp/src/app/routes/identity/module/module-edit.component.ts 文件,如下所示.
可以看到,代码非常简单,没有加载父模块下拉树组件的代码.
/**
* 模块编辑页
*/
@Component({
selector: 'module-edit',
templateUrl: environment.production ? './html/edit.component.html' : '/view/routes/identity/module/edit'
})
export class ModuleEditComponent extends TreeEditComponentBase<ModuleViewModel> {
/**
* 查询参数
*/
queryParam: ResourceQuery;
/**
* 应用程序标识
*/
@Input() applicationId;
/**
* 应用程序名称
*/
@Input() applicationName;
/**
* 初始化模块编辑页
* @param injector 注入器
*/
constructor(injector: Injector) {
super(injector);
let param = this.util.dialog.getData<any>();
if (param) {
this.applicationId = param.applicationId;
this.applicationName = param.applicationName;
}
}
/**
* 初始化
*/
ngOnInit() {
super.ngOnInit();
this.model.applicationId = this.applicationId;
this.queryParam = this.createQuery();
}
/**
* 创建模型
*/
createModel() {
let result = new ModuleViewModel();
result.enabled = true;
return result;
}
/**
* 创建查询参数
*/
protected createQuery() {
let result = new ResourceQuery();
result.applicationId = this.applicationId;
return result;
}
/**
* 获取基地址
*/
protected getBaseUrl() {
return "module";
}
/**
* 选择图标
*/
selectIcon(icon) {
this.model.icon = icon;
}
}
最后,查看 Web Api 控制器的代码.
打开 Util.Platform.Api/Controllers/Identity/ModuleController.cs 文件,如下所示.
模块控制器从 NgZorroTreeControllerBase 基类继承,已经封装将树形数据转换成 Ng Zorro 树形组件需要的数据格式.
NgZorroTreeControllerBase 基类提供的 QueryAsync 方法用于为树形表格提供数据,TreeQueryAsync 方法为树形或下拉树形提供数据.
查看操作资源
点击 模块资源 列表 资源设置 按钮,打开 操作资源 列表, 如下所示.
操作资源 列表是一个内嵌表格,为模块资源提供细粒度访问控制,比如控制页面上的按钮.
资源还有两种常见类型, 列 资源和 行集 资源.
列 资源用来控制显示的字段.
行集 资源用来控制不同角色显示不同的内容,俗称数据权限.
行集资源需要使用规约对象封装查询条件,并接入 Util 授权体系.
这两种资源尚未实现,待基础功能完善以后提供,如果你的项目对数据权限控制有需求,可以告知,我们将提前实现它.
点击 操作资源 创建 按钮,打开 创建操作权限 对话框.
操作名称输入框提示常用操作,可以从中选择,或手工输入.
资源最重要的属性是标识.
对于UI按钮的控制,可以使用有意义的标识, 比如 application.create, 表示创建应用程序.
资源的定义由开发人员根据控制粒度决定,除了在管理系统添加资源数据,还需要修改相应代码.
设置操作权限
下面以 创建应用程序 操作为例,介绍控制 UI 按钮和 API 需要的步骤.
添加 创建应用程序 操作资源.
数据迁移已经创建了该资源,标识为 application.create.
UI 按钮访问控制
打开 Util.Platform.Ui.Identity/Pages/Routes/Identity/Application/Index.cshtml 文件,如下所示.
只需要把 application.create 操作资源标识设置到 acl 属性即可.
acl 属性是 Ng Alain 提供的访问控制指令 *aclIf, 已经将 acl 属性全局添加到 Util UI所有 TagHelper 标签.
Api 访问控制
打开 Util.Platform.Api/Controllers/Identity/ApplicationController.cs 文件,代码简化如下.
c#
/// <summary>
/// 应用程序控制器
/// </summary>
[Acl( "application.view" )]
public class ApplicationController : CrudControllerBase<ApplicationDto, ApplicationQuery> {
/// <summary>
/// 初始化应用程序控制器
/// </summary>
/// <param name="service">应用程序服务</param>
public ApplicationController( IApplicationService service ) : base( service ) {
ApplicationService = service;
}
/// <summary>
/// 应用程序服务
/// </summary>
protected IApplicationService ApplicationService { get; }
/// <summary>
/// 创建
/// </summary>
/// <param name="request">创建参数</param>
[HttpPost]
[Acl( "application.create" )]
public new async Task<IActionResult> CreateAsync( ApplicationDto request ) {
return await base.CreateAsync( request );
}
}
注意 ApplicationController 控制器上面的 [Acl( "application.view" )] 特性.
Asp.Net Core 自带了一个授权特性 [Authorize] ,它默认的行为是只要登录认证成功,就能进行任何操作.
Authorize 特性可以设置 Roles 属性, 设置硬编码的角色列表,这不太灵活.
Authorize 特性还能设置 Policy 自定义授权策略, 这是一种灵活的授权方式,但每个需要授权的API都要设置 Policy 很费力.
Util Acl 特性从 Authorize 继承,并封装了特定的授权策略.
与 Authorize 的 Roles 不同的是, Acl 并不设置角色,因为角色是动态的,可能随时增加减少,所以设置角色是一种不太靠谱的方法,只适合角色固定的项目.
Acl 特性设置的是资源标识, 资源标识是固定不变的,理解这一点是将权限从业务逻辑中剥离出来的关键.
application.view 是查看应用程序操作资源.
任何功能页面都需要查看权限,如果没有细粒度控制需求,对整个页面和API进行控制即可.
现在只要某个角色拥有 application.view 操作资源,他就能对控制器中所有方法进行访问.
当需要进行更加细粒度的控制时,只需要在某个控制器方法上添加 [Acl] 特性,并设置相应的资源标识即可.
CreateAsync 方法添加了 [Acl( "application.create" )] 特性,这说明只有拥有 application.create 资源的角色才能进行创建操作.
统一 UI 按钮与 API 操作的资源标识是权限设置的最佳实践.
另一种方法是UI按钮与API操作使用不同的资源标识,再将API资源绑定到UI按钮,这种方式更复杂且容易出错.
如果仅对 Web Api 进行访问控制, 可以使用API资源, 资源标识为 Api 地址,不需要在每个控制器打上 [Acl] 特性,Util 还提供了Acl过滤器,可以全局启用访问控制.
拥有细粒度操作资源的角色必须拥有查看资源.
对于本例,如果需要访问 CreateAsync 操作,Asp.Net Core首先验证 application.view 资源是否能访问,如果通过才会继续验证 application.create 资源.
授予权限
一旦创建操作资源,并在 UI 和 API 进行了Acl设置,就可以为角色授权.
数据迁移默认创建了两个角色,admin 和 test,并已将用户 test 关联到 test 角色,以及将用户 admin 关联到 admin角色.
点击左侧 角色 菜单,打开角色列表.
角色列表中点击 test 角色 权限设置 链接,如下所示.
弹出 角色权限设置对话框,如下所示.
默认 test 角色只能查看应用程序.
验证操作权限
下面来验证权限控制是否生效.
验证 UI 按钮权限
鼠标移到右上方头像,弹出菜单点击 退出登录 链接.
使用 test 用户登录,密码 test .
可以看到,只有一个应用程序菜单,并且操作按钮也消失了.
为 test 角色授予创建权限
退出登录,使用 admin 用户登录.
打开 test 角色 权限设置 窗口,勾选 授予 应用程序 的 创建 复选框,点击确定按钮,如下所示.
还支持拒绝权限,如果用户的某个角色拥有拒绝资源,则无法访问该资源.
退出登录,使用 test 用户登录.
打开应用程序页面,可以看到创建按钮已经显示出来.
验证 Api 权限
打开 Swagger ,之前已经测试过创建应用程序 API 接口无法访问,下面再来试试.
提交下面的数据.
json
{
"code": "test",
"name": "test",
"enabled": true
}
提交返回操作成功消息,说明权限设置生效.
全局关闭访问控制
一旦 Web Api 控制器添加了 [Acl] 特性,权限就已经启用.
对于某些场景可能相当麻烦,比如 Web Api 集成测试.
权限是由 Identity Server 提供的,你的集成测试需要从 Identity Server 获取令牌,这造成了不必要的负担.
Util应用框架支持全局关闭访问控制,如下所示.
c#
.AddAcl( t => t.AllowAnonymous = true )
AddAcl 配置 Util访问控制相关的服务依赖,AllowAnonymous让所有添加了 [Acl] 的方法跳过权限检测,无需登录,无需授权.