abp中多种登陆用户的设计
项目地址:https://gitee.com/bxjg1987/abp
场景
在《学校管理系统》中,学生、家长、教师、教务都可能登陆,做一些属于他们自己的操作。这些用户需要的属性各不相同,比如学生有学号,而教师没有。
应用程序用户
在编码时,经常需要获取当前登陆用户的信息,这个当前登陆用户就是应用程序用户。asp.net提供了一整套方案来实现应用程序用户,包括身份验证、授权、asp.net identity等
应用程序用户与业务场景中的用户不同,应用程序用户只需要区别是谁,最简单情况只需要知道用户id,它不关心这个用户具体是教师还是学生或其它类型的用户。基于这个用户id还可以实现角色、授权等操作。 浅显点理解应用程序用户主要是识别用户id,方便实现角色和授权
而“学生”、“教师”则是《学校管理系统》这个场景中的具体业务概念。
abp中有个abp zero模块,它已实现应用程序用户管理、登陆、角色、授权等功能。
与abp用户一对一关联
当业务用户需要登陆时,一对一关联到应用程序用户。*
这样系统本身提供的登陆、注销、角色、授权等功能几乎保持不变,按需要可以实现多种业务用户类型。
模块化
我们的系统是按业务分模块开发的,参考:https://www.bilibili.com/video/BV1b5411L7Hf/
有些业务模块可能存在业务用户,比如【商城模块】中的“顾客”,在设计时需要考虑模块化和可扩展性。
由于是独立模块,所以我们不知道将来模块被什么系统引用,因此也不知道具体的AbpUser类型,因为那是模块使用方自己定义的,因此在开发业务模块时不应该出现具体的AbpUser的类型,需要关联时只能关联Id,必要时可以引用抽象的AbpUser及其管理类。
Core层
按ddd和abp的方式这层需要建立:聚合根、实体、值对象、领域服务、领域事件、仓储接口。
下面以《学校管理系统》场景说明
实体
按ddd方式定义学生实体(它应该是聚合根)。
定义学号、所属学院、所属专业等属性,重点是它有个UserId属性,关联到应用程序用户,注意不要使用导航属性关联到AbpUser,一来ddd建议一个聚合中的实体不要使用导航属性关联到另一个聚合中的实体,二来我们使用的是模块化开发方式,我们在开发自己的模块时并不会知道模块将来被谁使用,因此就不会知道具体的类型,因为在abp中,用户是由开发人员自定义的。
不要考虑模块使用方使用继承来扩展实体,建议使用abp提供的IExtensionObject接口或动态属性系统。
领域实体中可以触发事件,以便模块调用方扩展
领域服务、领域事件、仓储接口。
这个根据需要决定是否定义。
领域服务可以提供虚方法以便模块使用方法提供子类重写,也可以触发相应事件让模块使用方去订阅。
由于将来可能增加更多用户类型,领域服务可以考虑抽象封装在BXJG.Utils(依赖abp的通用功能模块)中
EFCore层
ef映射和种子数据的处理
Application层
新增业务用户时考虑同时建立并关联用户、删除时则一并删除。
可以提供虚方法,以便模块调用方继承并重写
在模块中,没有将新增、删除应用程序用户的逻辑预留给模块使用方,而是在模块内部直接做了,模块调用方可以订阅UserCreating、UserDeleting事件来插入自己的逻辑
由于将来可能增加更多用户类型,应用服务可以考虑抽象封装在BXJG.Utils.Application中
session与登陆
如《学生管理系统》有个学生后台,里面全都是学生可以操作的功能,做这些功能时通常需要获取当前登陆学生的id。abp的seession功能只能获取当前登陆用户id,这是abpUser的id,这个id并不是我们需要的,它与学生实体是一对一关联的。
我们可以通过abp的seesion获取当前abpUser的id,然后去对应用户表查询得到业务用户的id,但这样比较浪费性能。
我们的思路是在用户登陆时将关联的业务场景用户的id存储到claim中,然后提供一个类似abpsession的session来在需要是提供当前业务用户的id
我们可以为每种用户建立登陆页面,在登陆时存储业务用户id到claim中,也可以在统一的登陆页面加各判断,然后获取对应业务用户类型表中的业务用户id
session的设计也可以抽象出来,因为有多种用户类型
如何使用
abp官方文档教程中有[扩展session](https://aspnetboilerplate.com/Pages/Documents/Articles%5CHow-To%5Cadd-custom-session-field-aspnet-core)的说明,我们也是按这个思路做的。由于系统可能存在多种业务用户,因此需要简单封装下,下面看看商城模块中的顾客是如何实现session和登陆的
实体实现IBusinessUserEntity
1 public class CustomerEntity : FullAuditedAggregateRoot<long>, IBusinessUserEntity , IMustHaveTenant, IExtendableObject
2 //略....
定义session接口和实现
1 public interface ICustomerSession : IBusinessUserSession<long>{}
2
3 public class CustomerClaimSession : BusinessUserClaimSession<long>, ICustomerSession
4 {
5 public CustomerClaimSession(IPrincipalAccessor principalAccessor,
6 IMultiTenancyConfig multiTenancy,
7 ITenantResolver tenantResolver,
8 IAmbientScopeProvider<SessionOverride> sessionOverrideScopeProvider) : base(principalAccessor, multiTenancy, tenantResolver, sessionOverrideScopeProvider, CoreConsts.CustomerIdClaim)
9 {
10 }
11 }
在模块中实现ioc注入
1 public override void Initialize()
2 {
3 IocManager.IocContainer.Register(Component.For<ICustomerSession>()
4 .ImplementedBy<CustomerClaimSession>()
5 .LifestyleCustom<MsScopedLifestyleManager>()
6 .Named("sdf234sdf"));//CustomerClaimSession的父类已单例注册了,需要重命名下
7
8 }
定义登陆器接口和实现
public interface ICustomerLoginManager<TUser> : IBusinessUserLoginManager<TUser> { } /// <summary>
/// 提供与顾客登陆相关功能
/// </summary>
public class CustomerLoginManager<TTenant,
TRole,
TUser,
TUserManager> : BusinessUserLoginManager<CustomerEntity,
long,
TTenant,
TRole,
TUser,
TUserManager>, ICustomerLoginManager<TUser>
where TTenant : AbpTenant<TUser>
where TRole : AbpRole<TUser>, new()
where TUser : AbpUser<TUser>
where TUserManager : AbpUserManager<TRole, TUser>
{
public CustomerLoginManager(IRepository<CustomerEntity, long> repository,
TUserManager userManager) : base(repository,
userManager,
CoreConsts.CustomerRoleName,
CoreConsts.CustomerIdClaim)
{ }
}
主程序的XXX.Core的Module类中添加依赖注入
1 IocManager.Register<ICustomerLoginManager<User>, CustomerLoginManager<Tenant, Role, User, UserManager>>(Abp.Dependency.DependencyLifeStyle.Transient);
使用登陆器
最后在主程序的UserClaimsPrincipalFactory中
1 public class UserClaimsPrincipalFactory : AbpUserClaimsPrincipalFactory<User, Role>
2 {
3 private readonly ICustomerLoginManager<User> customerLoginManager;
4 public UserClaimsPrincipalFactory(
5 UserManager userManager,
6 RoleManager roleManager,
7 IOptions<IdentityOptions> optionsAccessor, ICustomerLoginManager<User> customerLoginManager)
8 : base(
9 userManager,
10 roleManager,
11 optionsAccessor)
12 {
13 this.customerLoginManager = customerLoginManager;
14 }
15 public override async Task<ClaimsPrincipal> CreateAsync(User user)
16 {
17 var claim = await base.CreateAsync(user);
18 var c = await customerLoginManager.GetBusinessUserClaim(user);
19 if(c!=null)
20 claim.Identities.First().AddClaim(c);
21 return claim;
22 }
23 }
流程说明
登陆时AbpLoginManager会调用UserClaimsPrincipalFactory来向当前登陆用户的Claims中插入Claim
UserClaimsPrincipalFactory会调用ICustomerLoginManager,先判断用户是否是顾客的角色,若是则根据用户Id找到顾客Id,然后将顾客Id存储到Claims中
后续我们在需要获取当前登陆的顾客的id时在我们的服务中依赖注入ICustomerSession就可以了,它会从当前登陆用户的Claims中去找到顾客id
abp中多种登陆用户的设计的更多相关文章
- JavaWeb-SpringSecurity在数据库中查询登陆用户
系列博文 项目已上传至guthub 传送门 JavaWeb-SpringSecurity初认识 传送门 JavaWeb-SpringSecurity在数据库中查询登陆用户 传送门 JavaWeb-Sp ...
- 在ABP VNext框架中处理和用户相关的多对多的关系
前面介绍了一些ABP VNext架构上的内容,随着内容的细化,我们会发现ABP VNext框架中的Entity Framework处理表之间的引用关系还是比较麻烦的,一不小心就容易出错了,本篇随笔介绍 ...
- C#开发微信门户及应用(10)--在管理系统中同步微信用户分组信息
在前面几篇文章中,逐步从原有微信的API封装的基础上过渡到微信应用平台管理系统里面,逐步介绍管理系统中的微信数据的界面设计,以及相关的处理操作过程的逻辑和代码,希望从更高一个层次,向大家介绍微信的应用 ...
- [2018-12-18]ABP中的AsyncCrudAppService介绍
前言 自从写完上次略长的<用ABP入门DDD>后,针对ABP框架的项目模板初始化,我写了个命令行工具Abp-CLI,其中子命令abplus init可以从github拉取项目模板以初始化项 ...
- Java生鲜电商平台-电商中海量搜索ElasticSearch架构设计实战与源码解析
Java生鲜电商平台-电商中海量搜索ElasticSearch架构设计实战与源码解析 生鲜电商搜索引擎的特点 众所周知,标准的搜索引擎主要分成三个大的部分,第一步是爬虫系统,第二步是数据分析,第三步才 ...
- 基于 Egg.js 框架的 Node.js 服务构建之用户管理设计
前言 近来公司需要构建一套 EMM(Enterprise Mobility Management)的管理平台,就这种面向企业的应用管理本身需要考虑的需求是十分复杂的,技术层面管理端和服务端构建是架构核 ...
- ABP源码分析四十七:ABP中的异常处理
ABP 中异常处理的思路是很清晰的.一共五种类型的异常类. AbpInitializationException用于封装ABP初始化过程中出现的异常,只要抛出AbpInitializationExce ...
- Apple、Google、Microsoft的用户体验设计原则
轻巧的Apple 注重设计过程: 在设计过程中引入用户交互的5个目标: 了解您的目标客户 分析用户的工作流 构造原型系统 观察用户测试 制定观察用户准则 做出设计决定 避免功能泛滥 80% 方案 优秀 ...
- ABP中使用OAuth2(Resource Owner Password Credentials Grant模式)
ABP目前的认证方式有两种,一种是基于Cookie的登录认证,一种是基于token的登录认证.使用Cookie的认证方式一般在PC端用得比较多,使用token的认证方式一般在移动端用得比较多.ABP自 ...
随机推荐
- Angular写一个Form组件-TagInput
前端开发少不了和表单打交道; Angular中, 提供了强大的表单的支持, 响应式表单(Reactive Form) 和 模板驱动的表单(Template-driven Form) 的双向数据流给我们 ...
- hdu1217 Arbitrage
Problem Description Arbitrage is the use of discrepancies in currency exchange rates to transform on ...
- L2-013 红色警报 (25分) 并查集复杂度
代码: 1 /* 2 这道题也是简单并查集,并查集复杂度: 3 空间复杂度为O(N),建立一个集合的时间复杂度为O(1),N次合并M查找的时间复杂度为O(M Alpha(N)), 4 这里Alpha是 ...
- Yocto项目介绍及入门 -- 嵌入师工程师必备利器
目录 写在前面 1. Yocto项目是什么 2. Yocto项目有什么用 3. 如何快速上手Yocto项目 4. 带你通过Yocto项目编译一个自定义镜像文件 写在前面 博主目前从事BMC工作,由于公 ...
- VScode 相关
1.F5运行py文件,打开terminal终端的时候总是弹出Powershell窗口,只能在powershell窗口中用命令行运行程序,实在很不方便. 解法:右键Powershell属性,取消使用旧版 ...
- Redis 多实例 & 主从复制
Redis 多实例 多实例目录 [root@db01 ~]# mkdir /service/redis/{6380,6381} 多实例配置文件 # 第一台多实例配置 [root@db01 ~]# vi ...
- 使用SignTool对软件安装包进行数字签名(二)--进行数字签名
四.使用signcode.exe为安装程序.库或cab包签名 1.运行signcode.exe. 2.点击"下一步",选择需要签名的文件(安装程序.库或cab包). 3.点击&qu ...
- 谈一谈phar 反序列化
前言 来自Secarma的安全研究员Sam Thomas发现了一种新的漏洞利用方式,可以在不使用php函数unserialize()的前提下,引起严重的php对象注入漏洞.这个新的攻击方式被他公开在了 ...
- Web 前端 UI 组件库文档自动化方案 All In One
Web 前端 UI 组件库文档自动化方案 All In One 需求 自动化 动态 好用 markdown element-ui 中示例和说明按照一定规则写在md文件中,调用md-loader将md文 ...
- git tag All In One
git tag All In One $ git tag --help # (cedec380)在指定的分支上打 tag $ git tag -a stable-version-1.1.1 cedec ...