Identity 领域能力指南
Identity 模块(src/Services/Identity/FreeKit.Identity)基于 ASP.NET Core Identity + FreeSql 构建,提供一整套用户、角色、权限、租户、组织架构、设备与密钥的建模与运行时能力。本文档面向开发者,聚焦"模块提供什么能力、概念如何关联、为什么这样设计、在业务代码里怎么调用",不罗列实体字段(实体细节可在源码 FreeKit.Identity/Models 中查阅)。
如果你要的是某一块的深度用法,请跳到对应文档:
- 认证/SSO 流程 → SSO · 设备验证
- 横切能力(文件、设置、附件、字典)→ Infrastructure 开发指南
- 完整端到端示例 → 开发指南
- 轻量版 / 会员版 → BasicIdentity · Member
1. 模块边界与子域划分
Identity 不是"一个用户表 + 一堆接口",而是一组围绕"身份与访问"的内聚子域。它们共享同一个身份主体 IdentityUser,但在职责边界上彼此分离:
| 子域 | 负责什么 | 不负责什么 |
|---|---|---|
| 认证(Account) | 登录/注册/注销/刷新 Token/模拟/邮箱校验改密 | 不直接做权限判定(交给 Permission 子域) |
| 用户(User/Profile) | 跨身份基础查询、管理员锁定/重置密码、当前用户资料/头像/改密/改用户名 | 不提供"用户 CRUD",创建走注册、员工维度走 Employee |
| 角色(Role) | 角色定义、默认/公开/静态角色、数据范围 | 不持有权限定义本身 |
| 权限(Permission) | 权限树、赋权(用户/角色/岗位)、权限检查、复制 | 不负责登录 |
| 租户(Tenant) | 租户隔离、切换、邀请成员 | 不负责用户注册 |
| 组织架构(OrgUnit/Position/Employee) | 部门树、岗位、员工档案与调岗 | 不负责认证 |
| 设备(Device) | 登录设备指纹、信任、新设备风控 | 不独立成服务,内嵌于登录流程 |
| 双因素(2FA) | 启用/确认/禁用、登录验证 | 依赖邮箱通道 |
| 安全(AccessKey/OnlineUser/UserToken) | API 密钥、在线用户、Token 吊销、请求日志 | — |
| 社交登录(OAuth2/OpenIddict) | 第三方绑定/解绑/登录、OIDC 客户端管理 | — |
| 个性化(Shortcut/TableColumnSetting) | 用户快捷菜单、表格列设置 | — |
设计要点:没有独立的"用户 CRUD"控制器。
UserController(/api/identity/users)只提供列表与详情查询;创建用户走注册(AccountController.register),资料修改走ProfileController,员工维度的管理走EmployeeController。这是有意把"用户生命周期"拆分到不同职责边界——注册、资料、员工档案各自演进,互不耦合。
2. 核心关系
下面是模块内核心概念的关联关系(基于真实实体导航与代码证据,非示意):
要点解读:
- 权限的授予对象是多态的。
PermissionGrant(Models/Identity/PermissionGrant.cs)用PermissionGrantType区分R(角色)/U(用户)/P(岗位),统一挂在Permission上。这意味着同一个权限可分别授给用户、角色或岗位,解析时三条来源合并生效。 - 组织关系走"员工档案"而非用户实体本身。
IdentityUser只暴露OrgUnits/Positions集合用于便捷查询;真正承载"某人在某部门任某岗"的是EmployeeInfo(UserId+OrgUnitId+PositionId+LeaderUserId)。 - 设备/密钥/Token 都是用户的从属。
UserLoginDevice、UserAccessKey、IdentityUserToken、RefreshToken都以UserId为外键,删除用户时相关凭据一并失效。 - 租户是多对多的软边界。
IdentityUser.Tenants集合 +IdentityTenant表,配合中间件IdentityTenantResolver在请求期确定当前租户。
3. 各子域:设计动机与业务能力
下面按子域展开"为什么这样设计"和"它提供什么业务能力"。所有动机均对应代码证据。
3.1 认证(Account)
- 能力:密码登录、邮箱验证码登录、注册、注销、刷新 Token、管理员模拟、邮箱校验与改密、验证码。
- 动机——生命周期拆分:注册是用户进入系统的唯一写入口(
AccountService.RegisterAsync),登录只做校验与签发,不做用户创建。管理员模拟(AccountService.ImpersonateAsync)会保留impersonator与原始租户信息写入 Token Claim,便于审计与回退。 - 动机——验证与签发分离:
ITokenHandler专职签发/校验 JWT,IRefreshTokenService管理刷新令牌轮换,AccountService只做编排。这样 Token 策略(有效期、刷新窗口)可独立演进。
3.2 用户与资料(User / Profile)
- 能力:跨身份基础查询(SSO 选人、选主管、按 Id 查找)、当前用户资料、头像、昵称、改密码、改用户名。
- 动机——查询与写入分离:
IUserService只提供只读查询(UserController仅两个HttpGet);写操作被拆到ProfileService(改资料/头像/密码/用户名)和注册流程。在领域事件层面,IdentityUser实现了IDomainEventBase,会在创建/变更/硬删除/登录时抛出UserCreateDomainEvent/UserChangeDomainEvent/UserHardDeleteDomainEvent/UserLastLoggedInAtDomainEvent,由对应 Handler 落地(如登录时间更新、会员档案联动)。
3.3 角色(Role)
- 能力:角色 CRUD、默认角色(新用户注册自动分配)、公开角色(其他用户可见)、静态角色(内置不可删)、数据范围(
DataScope)。 - 动机——角色是权限的容器:角色本身不定义权限,只通过
PermissionGrant(R)关联权限树节点。默认角色让"注册即赋予基础权限"成为配置而非代码;静态角色防止被误删导致授权链断裂。
3.4 权限(Permission)
- 能力:权限树(自引用
ParentId/Children,PermissionType分Folder/Menu/Element/Api)、赋权(用户/角色/岗位)、权限检查、角色/用户权限复制。 - 动机——权限定义与代码强一致(关键设计):
PermissionManager.InitPermissionAsync()在启动时调用PermissionUtil.GetFreeKitAuthorizeAttributes(),通过反射扫描所有Controller的[Authorize(Policy=...)]与[KitAuthorize]特性,把每个受保护接口同步进权限树。这样权限点不会因代码改动而"漏配"或"漂移"——你加了带Policy的接口,权限树就多一个节点;手工维护成本降到最低。解析时Admin角色直接放行,其余查PermissionGrant的 U/R/P 三来源合并判定。
3.5 租户(Tenant)
- 能力:租户 CRUD、切换(返回换租户后的新 Token)、邀请成员、成员列表。
- 动机——多租户隔离优先级明确:
IdentityTenantResolver.Resolve(HttpContext)的解析顺序为X-Tenant-Id请求头 > JWT 中的tenant_id声明。请求头优先,方便服务端间调用显式指定租户;登录态下回落到 Token 声明,保证用户自身数据隔离。切换租户会重新签发 Token 并把新tenant_id写入 Claim(TenantService)。
3.6 组织架构(OrgUnit / Position / Employee)
- 能力:组织单元树(
ParentId自引用,编码Code按层级自动生成如00001.00042.00005)、职位(绑定OrgUnitId、编制HeadCount、是否主管岗)、员工档案(部门/岗位/直属上级/工号/在职状态)及调岗。登录锁定与管理员密码重置属于用户能力。 - 动机——组织是"视图"而非"身份":用户身份(
IdentityUser)稳定,而人在组织里的位置(EmployeeInfo)会变化。把员工维度独立出来,使调岗/离职不影响登录身份,也避免用户表被组织字段污染。
3.7 设备(Device)
- 能力:登录设备指纹记录、信任判定、新设备风控;内嵌于登录流程,无独立服务类。
- 动机——新设备风控:设备指纹 =
SHA256(UserAgent)前 32 位(UserLoginDeviceManager),IP 变化不影响识别。信任有效期由Device:TrustExpireDays(默认180天)控制;新设备行为由设置Device.NewDeviceMode(默认notify)决定——notify仅发提醒邮件不阻断,verify要求邮箱验证码(AccountService登录时读取该值决策)。信任过期后下次登录重新验证。
3.8 双因素(2FA)
- 能力:启用(发确认邮件)、确认、禁用、登录验证。
- 动机——与设备风控共用"二次验证"出口:2FA 开启时,登录一律要求验证码(
TwoStepVerificationService通过emailFactory.Send2faEmailAsync/SendEnable2faEmailAsync发码)。它与"未受信设备"共用同一套VerifyToken验证通道,逻辑统一。
3.9 安全(AccessKey / OnlineUser / UserToken)
- 能力:AccessKey 管理(
UserAccessKey含AccessKeyId/Secret、状态、最近使用时间、操作日志UserAccessKeyLog)、在线用户、UserToken 吊销、请求日志。 - 动机——服务间调用的稳定身份:AccessKey 给非浏览器/脚本/微服务一套长期可用的凭证,与用户会话 Token 解耦;吊销 Token 只影响当前会话,不影响 AccessKey。
3.10 社交登录(OAuth2 / OpenIddict)
- 能力:OAuth2 绑定/解绑/登录(GitHub/Gitee/QQ/微信/Google/Microsoft/KitSSO)、OpenIddict 客户端与应用管理。
- 动机——身份来源多元化:第三方登录通过
IdentityUserLogin绑定到同一IdentityUser,保证"多种登录方式 = 同一个身份"。
3.11 个性化(Shortcut / TableColumnSetting)
- 能力:用户快捷菜单、表格列设置。
- 动机——界面偏好是用户态而非系统态:这些设置按用户隔离,属于身份主体的体验延伸。
4. 核心服务接口
开发者在业务代码里通常注入以下 应用服务(均实现 IApplicationService,来自 FreeKit.Identity.Application.*.Contracts):
IAccountService— 登录/注册/邮箱校验/改密/刷新 Token 的编排入口。IUserService— 跨身份基础查询(SSO 选人、选主管、通用按 Id 查找),不负责写。IProfileService— 当前用户资料、头像、昵称、改密码、改用户名。IRoleService/IPermissionService/ITenantService/IOrgUnitService/IPositionService/IEmployeeService— 各自 CRUD(多数实现ICrudAppService)。IUserAccessKeyService/IOnlineUserService/IUserTokenService/IUserShortcutService/ITableColumnSettingService— 安全与个性化。IOAuth2Service/IOAuth2AdminService— 社交登录与 OpenIddict 客户端。ITwoStepVerificationService/IRefreshTokenService/IUserLoginService/ILinkUserService/ISerilogService/IRequestLogService。
领域层(来自 FreeKit.Identity.Domain)的 Manager 提供更细粒度的建模能力,框架/内部使用较多:
IdentityUserManager(命名空间FreeKit.Identity.Domain.Identity)— ASP.NET Identity 用户管理扩展。IUserLoginDeviceManager(UserLoginDeviceManager)— 设备指纹信任、新设备处理、信任过期(Device:TrustExpireDays)。IPermissionManager— 权限定义初始化与解析(启动时扫描控制器[Authorize]/[KitAuthorize]同步权限树)。IdentityLinkUserManager/TwoStepVerificationService(内部聚合,依赖IdentityUserManager+ITokenHandler)。
5. 配置(业务含义)
认证与 Token
{
"Authentication": {
"ExpiresTime": 86400, // AccessToken 有效期(秒),由 TokenHandler 读取
"RefreshExpiresIn": 2592000 // RefreshToken 有效期(秒)
}
}
TokenHandler 还会回退读取 Authentication:0:Schemes:ExpiresTime / Authentication:0:ExpiresIn 等多方案配置键。
设备信任
Device:TrustExpireDays(默认180天):信任设备有效期;到期需重新验证。由UserLoginDeviceManager读取。- 新设备行为由设置
Device.NewDeviceMode控制,取值notify(仅邮件通知)或verify(要求邮箱验证码);默认notify。LoginService在登录时读取该值决定流程。
密码与锁定策略(IdentityOptions)
在 IdentityModuleStartup 中通过 AddIdentityCore<IdentityUser> 配置,等价于标准 ASP.NET Identity 选项:
{
"IdentityOptions": {
"Password": {
"RequiredLength": 8,
"RequireDigit": true,
"RequireLowercase": true,
"RequireUppercase": false,
"RequireNonAlphanumeric": false
},
"Lockout": {
"MaxFailedAccessAttempts": 5,
"DefaultLockoutTimeSpan": "00:30:00"
}
}
}
注册与接入
// Program.cs —— 框架自动发现并启动 IdentityModuleStartup
builder.Services.AddFreeKitCore(); // 基础能力
// 聚合主宿主注册 IdentityModuleStartup(含上述 IdentityOptions、FreeSql、SignalR 等)
dotnet run --project src/Services/Host/FreeKit.Host
# Swagger: https://localhost:7000/kit_api/swagger
6. 开发示例
// 注入应用服务即可调用(示例:给当前用户授予某个权限)
public class MyService(IPermissionService permissionService, IUserService userService)
{
public async Task GrantAsync(Guid userId, string[] codes)
{
await permissionService.GrantPermissionAsync(new PermissionGrantReq
{
ProviderKey = userId.ToString(),
PermissionGrantType = PermissionGrantType.U, // 用户级
PermissionNames = codes
});
}
}
// 登录时判断设备是否需要二次验证(注入领域服务 IUserLoginDeviceManager)
public class LoginGuard(IUserLoginDeviceManager deviceManager)
{
public async Task<bool> NeedsExtraVerifyAsync(Guid userId, string userAgent)
{
var devices = await deviceManager.GetDevicesAsync(userId, userAgent);
var current = devices.FirstOrDefault(d => d.IsCurrentDevice);
return current is null || !current.IsTrustedAndValid;
}
}