IdentityProvider 域统一了身份提供者(IdP)的用户与用户组管理能力。Okta 和 Keycloak 两家供应商共享同一个 IIdentityProviderService 接口,但鉴权机制完全不同:Okta 用 API Token(SSWS Bearer),Keycloak 用 client_credentials 换 OAuth2 token 并经 TokenManager 缓存刷新。
域结构
Bitzsoft.Integrations.IdentityProvider ← 抽象层├── Bitzsoft.Integrations.IdentityProvider.Okta ← Okta(API Token SSWS)├── Bitzsoft.Integrations.IdentityProvider.Keycloak ← Keycloak(client_credentials)└── Bitzsoft.Integrations.IdentityProvider.All ← 聚合包标准三层包模式。
统一接口(13 方法)
IIdentityProviderService 围绕”用户(User)“和”用户组(Group)“两个核心概念组织,共 13 个成员:
public interface IIdentityProviderService{ string ProviderName { get; }
// 用户生命周期(5) Task<IdentityUser> CreateUserAsync(CreateUserRequest request, CancellationToken cancellationToken = default); Task<IdentityUser> GetUserAsync(string userId, CancellationToken cancellationToken = default); Task<IdentityUser> UpdateUserAsync(string userId, UpdateUserRequest request, CancellationToken cancellationToken = default); Task DeleteUserAsync(string userId, CancellationToken cancellationToken = default); Task<List<IdentityUser>> ListUsersAsync(ListUsersRequest request, CancellationToken cancellationToken = default);
// 账户状态与凭证(3) Task ActivateUserAsync(string userId, CancellationToken cancellationToken = default); Task DeactivateUserAsync(string userId, CancellationToken cancellationToken = default); Task ResetPasswordAsync(string userId, ResetPasswordRequest request, CancellationToken cancellationToken = default);
// 用户组(5) Task<IdentityGroup> CreateGroupAsync(CreateGroupRequest request, CancellationToken cancellationToken = default); Task<IdentityGroup> UpdateGroupAsync(string groupId, UpdateGroupRequest request, CancellationToken cancellationToken = default); Task DeleteGroupAsync(string groupId, CancellationToken cancellationToken = default); Task<List<IdentityGroup>> ListGroupsAsync(CancellationToken cancellationToken = default); Task AddUserToGroupAsync(string userId, string groupId, CancellationToken cancellationToken = default);}查询类方法(Create / Get / Update / List)抛异常表达失败;账户状态与用户组操作多为无返回的 Task,失败同样抛 IdentityProviderException。
鉴权模型
两家供应商使用截然不同的鉴权机制:
| 供应商 | 鉴权机制 | Token 注入 | Token 刷新 |
|---|---|---|---|
| Okta | API Token(SSWS,Static Token) | Authorization: SSWS {apiToken} 头 | 静态令牌,不刷新(管理员手动轮换) |
| Keycloak | OAuth2 client_credentials(Client Id + Secret) | Authorization: Bearer {token} 头 | POST /realms/{realm}/protocol/openid-connect/token(TokenManager 缓存) |
Keycloak 的 TokenManager
Keycloak 的 token 有过期时间,由注册为 Singleton 的 TokenManager 负责缓存刷新,采用双重检查锁模式:快速路径无锁检查缓存、进锁后二次校验、提前 5 分钟刷新。HttpClient 检测到 401 时清缓存触发下次自动刷新。Okta 因使用静态 API Token,无需 TokenManager。
供应商清单
| 供应商 | Provider ID | API 基地址 | Stability | DI 方法 |
|---|---|---|---|---|
| Okta | okta | {your-domain}.okta-tenant.com/api | Preview | AddOktaIdentityProvider() |
| Keycloak | keycloak | {your-host}/realms/{realm} | Preview | AddKeycloakIdentityProvider() |
两家具备完整的用户与用户组管理能力,均标记为 Preview。
注册
单厂商
// ① Okta(API Token SSWS)builder.Services.AddOktaIdentityProvider(builder.Configuration.GetSection("IdentityProvider:Okta"));
// ② Keycloak(client_credentials + TokenManager)builder.Services.AddKeycloakIdentityProvider(builder.Configuration.GetSection("IdentityProvider:Keycloak"));全量聚合
builder.Services.AddBitzsoftIdentityProviderAll(builder.Configuration, "IdentityProvider");按配置节存在性自动注册,appsettings.json 只需写出启用的供应商:
{ "IdentityProvider": { "Okta": { "Domain": "https://your-org.okta.com", "ApiToken": "00...", "ApiVersion": "1" }, "Keycloak": { "ServerUrl": "https://your-host/auth", "Realm": "master", "ClientId": "admin-cli", "ClientSecret": "..." } }}消费
用户生命周期
public class UserProvisioningService(IIntegrationProviderResolver<IIdentityProviderService> providers){ public async Task<IdentityUser> OnboardAsync(string vendor, string email, string firstName, string lastName) { var idp = providers.GetRequired(vendor); // "okta" / "keycloak" return await idp.CreateUserAsync(new CreateUserRequest { Email = email, FirstName = firstName, LastName = lastName, IsActive = true, }); }}激活/停用与重置密码
// 停用离职用户await idp.DeactivateUserAsync(userId);
// 重置密码(临时密码,下次登录强制改)await idp.ResetPasswordAsync(userId, new ResetPasswordRequest{ NewPassword = tempPassword, Temporary = true,});用户组管理
// 创建部门用户组并加入成员var group = await idp.CreateGroupAsync(new CreateGroupRequest{ Name = "engineering", Description = "工程团队",});await idp.AddUserToGroupAsync(userId, group.Id);多厂商路由
public class IdpRouter(IIntegrationProviderResolver<IIdentityProviderService> providers){ public async Task<IdentityUser> GetUserAsync(string source, string userId) { var idp = providers.GetRequired(source); // "okta" / "keycloak" return await idp.GetUserAsync(userId); }}