当前位置: 首页 > news >正文

ABP vNext + OpenIddict:自定义 OAuth2/OpenID Connect 认证策略

ABP vNext + OpenIddict:自定义 OAuth2/OpenID Connect 认证策略 🚀


📑 目录

  • ABP vNext + OpenIddict:自定义 OAuth2/OpenID Connect 认证策略 🚀
    • 🧠 背景与核心设计思路
    • 🛠 依赖注入与启动配置
    • 🔑 系统配置:注册 Token 授权管道
    • 🔧 自定义授权处理器:ApiKeyGrantHandler
    • 🏢 租户解析与多租户 SSO
      • Contributor 实现
      • 注入配置
      • 上下文切换
        • 🌀 多租户解析流程
    • 📋 接口定义:IApiKeyValidator
    • 📈 Scope & Client 动态管理
        • 🔄 Client 管理流程
    • 🧪 接口调用示例
      • 1. API Key 授权成功
      • 2. 刷新令牌示例
        • 🔄 刷新流程图
    • 🔐 安全加固建议
    • 📁 项目结构推荐


🧠 背景与核心设计思路

大型 SaaS 系统常见需求:

  1. 自定义身份来源(API Key、Device Flow)
  2. 多租户隔离与 SSO
  3. 精细化 Scope/资源管理

ABP 的 OpenIddict 模块提供 Handler 模型 插槽,轻松插入自定义授权逻辑。


🛠 依赖注入与启动配置

public class AuthServerModule : AbpModule
{public override void ConfigureServices(ServiceConfigurationContext context){var services = context.Services;// ➤ 注册 OpenIddict 核心 + Server + Validationservices.AddOpenIddict().AddCore(options => { /* 实体存储等 */ }).AddServer(options => { /* 稍后配置 */ }).AddValidation(options => { /* 稍后配置 */ });// ➤ 多租户解析services.Configure<AbpTenantResolveOptions>(opts =>{opts.Resolvers.Insert(0, new HeaderTenantResolveContributor());opts.Resolvers.Insert(1, new DomainTenantResolveContributor());});}
}

🔑 系统配置:注册 Token 授权管道

PreConfigure<OpenIddictBuilder>(builder =>
{builder.AddServer(options =>{// —— 端点 ——  options.SetTokenEndpointUris("/connect/token").SetAuthorizationEndpointUris("/connect/authorize").SetDeviceEndpointUris("/connect/device");// —— Grant & Scope ——  options.RegisterGrantType("api_key_grant").AllowPasswordFlow().AllowClientCredentialsFlow().AllowRefreshTokenFlow().AllowExtensionGrantType("api_key_grant").SetDefaultScopes("api", "profile");// —— 有效期 ——  options.SetAccessTokenLifetime(TimeSpan.FromHours(2)).SetRefreshTokenLifetime(TimeSpan.FromDays(7));// —— ASP.NET Core 集成 ——  options.UseAspNetCore().EnableTokenEndpointPassthrough();// —— 自定义 Handler ——  options.AddEventHandler<HandleTokenRequestContext>(cfg =>cfg.UseScopedHandler<ApiKeyGrantHandler>().SetOrder(OpenIddictServerHandlers.Authentication.ValidateTokenRequest.Descriptor.Order + 1).SetFilter(ctx => ctx.Request.GrantType == "api_key_grant"));});builder.AddValidation(options =>{options.UseLocalServer();options.UseAspNetCore();});
});

🔧 自定义授权处理器:ApiKeyGrantHandler

public class ApiKeyGrantHandler : IOpenIddictServerHandler<HandleTokenRequestContext>
{private readonly IApiKeyValidator _apiKeyValidator;private readonly ICurrentTenant   _currentTenant;private readonly ILogger<ApiKeyGrantHandler> _logger;public ApiKeyGrantHandler(IApiKeyValidator apiKeyValidator,ICurrentTenant   currentTenant,ILogger<ApiKeyGrantHandler> logger){_apiKeyValidator = apiKeyValidator;_currentTenant   = currentTenant;_logger          = logger;}public async ValueTask HandleAsync(HandleTokenRequestContext context){using var scope = _logger.BeginScope(new { GrantType = "api_key" });try{var apiKey = context.Request.GetParameter("api_key")?.ToString();if (string.IsNullOrWhiteSpace(apiKey)){context.Reject(Errors.InvalidRequest, "Missing API Key");return;}var userId = await _apiKeyValidator.ValidateAsync(apiKey);if (string.IsNullOrEmpty(userId)){context.Reject(Errors.InvalidGrant, "Invalid API Key");return;}var tenantId = _currentTenant.Id?.ToString() ?? "default";var claims = new[]{new Claim(Claims.Subject,  userId),new Claim("tenant_id",    tenantId),new Claim(Claims.Name,     "API Key User")};var identity  = new ClaimsIdentity(claims, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);var principal = new ClaimsPrincipal(identity);// 设置 Scopes & Resource  principal.SetScopes(context.Request.GetScopes());principal.SetResources("api");// 顶层返回 tenant_id  context.AddParameter("tenant_id", tenantId);context.Validate(principal);context.HandleRequest();}catch (Exception ex){_logger.LogError(ex, "API Key grant failed.");context.Reject(Errors.ServerError, "Internal error.");}}
}

🏢 租户解析与多租户 SSO

Contributor 实现

public class DomainTenantResolveContributor : HttpTenantResolveContributorBase
{public override Task<string> ResolveAsync(HttpContext context){var sub = context.Request.Host.Host.Split('.').FirstOrDefault();return Task.FromResult(string.IsNullOrWhiteSpace(sub) ? "default" : sub);}
}public class HeaderTenantResolveContributor : HttpTenantResolveContributorBase
{public override Task<string> ResolveAsync(HttpContext context){var header = context.Request.Headers["X-Tenant-Id"].FirstOrDefault();return Task.FromResult(string.IsNullOrWhiteSpace(header) ? "default" : header);}
}

注入配置

services.Configure<AbpTenantResolveOptions>(opts =>
{opts.Resolvers.Insert(0, new HeaderTenantResolveContributor());opts.Resolvers.Insert(1, new DomainTenantResolveContributor());
});

上下文切换

using (_currentTenant.Change(tenantId))
{// 此作用域内,TenantId 生效  
}
🌀 多租户解析流程
Yes
No
Yes
No
Incoming HTTP Request
Has X-Tenant-Id Header?
Use HeaderTenantResolveContributor
Host Subdomain Exists?
Use DomainTenantResolveContributor
Fallback to default
Set CurrentTenant

📋 接口定义:IApiKeyValidator

public interface IApiKeyValidator
{/// <summary>/// 校验 API Key 并返回对应用户ID;失败返回 null/empty/// 实现可结合 IMemoryCache/IDistributedCache 缓存/// </summary>Task<string> ValidateAsync(string apiKey);
}

📈 Scope & Client 动态管理

[Authorize(Roles = "Admin")]
public class ScopeAppService : ApplicationService
{private readonly IOpenIddictScopeManager _scopeManager;public async Task<List<string>> GetScopesAsync(){var list = new List<string>();await foreach (var s in _scopeManager.ListAsync())list.Add(s.Name);return list;}
}[Authorize(Roles = "Admin")]
public class ClientAppService : ApplicationService
{private readonly IOpenIddictApplicationManager _appManager;public async Task CreateMobileClientAsync(){var desc = new OpenIddictApplicationDescriptor{ClientId    = "mobile_app",DisplayName = "Mobile App",Permissions ={Permissions.Endpoints.Token,Permissions.GrantTypes.Password}};await _appManager.CreateAsync(desc);}public async Task DeleteClientAsync(string clientId){var app = await _appManager.FindByClientIdAsync(clientId);if (app != null) await _appManager.DeleteAsync(app);}
}
🔄 Client 管理流程
Admin API Manager POST /api/clients (Create) CreateAsync(descriptor) App Created 201 Created DELETE /api/clients/{id} DeleteAsync(app) App Deleted 204 No Content Admin API Manager

🧪 接口调用示例

1. API Key 授权成功

POST /connect/token
Content-Type: application/x-www-form-urlencodedgrant_type=api_key_grant
api_key=valid-api-key
client_id=default
client_secret=secret
scope=api

成功响应:

{"access_token":"eyJhbGciOiJSUzI1NiIs...","token_type":"Bearer","expires_in":7200,"refresh_token":"eyJhbGciOiJIUzI1NiIs...","tenant_id":"tenant1"
}

错误示例:

{"error":"invalid_grant","error_description":"Invalid API Key"
}

2. 刷新令牌示例

POST /connect/token
grant_type=refresh_token
refresh_token={your_refresh_token}
client_id=default
client_secret=secret

刷新成功:

{"access_token":"…","token_type":"Bearer","expires_in":7200,"refresh_token":"…"
}

刷新失败:

{"error":"invalid_grant","error_description":"Refresh token is expired."
}
🔄 刷新流程图
Valid
Invalid
Client sends refresh_token
Server Validate Refresh Token
Issue new access_token & refresh_token
Return invalid_grant error

🔐 安全加固建议

🔒 类型🛡️ 实践建议
API Key哈希存储 + 过期 + 重放防护
限流使用 AspNetCoreRateLimit 保护 /connect/token
审计日志所有 Reject() 写入审计表,方便追踪
签名密钥DataProtection/RSA 证书 + 定期轮换
监控指标OpenTelemetry Meter/Counter 统计授权成功/失败

📁 项目结构推荐

AuthServer.Host
├── CustomGrants/
│   └── ApiKeyGrantHandler.cs
├── Tenanting/
│   ├── DomainTenantResolveContributor.cs
│   └── HeaderTenantResolveContributor.cs
├── Scopes/
│   └── ScopeAppService.cs
├── Clients/
│   └── ClientAppService.cs
├── Validation/
│   └── IApiKeyValidator.cs
├── OpenIddict/
│   └── PreConfiguration.cs
└── Program.cs

相关文章:

  • 如何从 Windows 11 或 10 远程访问 Ubuntu 24.04 或 22.04 桌面
  • 使用 C++ 和 OpenCV 构建智能停车场视觉管理系统
  • Linux NFS服务器配置
  • JavaScript数组方法总结
  • 每日Prompt:Steve Winter风格插画
  • PyTorch框架-自动微分模块
  • 将MySQL数据库中所有表和字段编码统一改为utf8mb4_unicode_ci
  • 影像组学5:Radiomics Score的计算
  • 系统常用线程池配置,使用与注意事项
  • 【Android】EventBus详解
  • 【测试开发】面向对象-魔术方法
  • 企业架构框架深入解析:TOGAF、Zachman Framework、FEAF与Gartner EA Framework
  • python打卡day53@浙大疏锦行
  • HarmonyOS 组件复用面试宝典 [特殊字符]
  • 《AI日报 · 0613|ChatGPT支持导出、Manus免费开放、GCP全球宕机》
  • 每天宜搭宜搭小知识—报表组件—柱线混合图
  • 【实用生信代码】分子对接后的分子动力学模拟实战——OpennMM
  • PH热榜 | 2025-06-13
  • 包含11个整套APP移动端UI的psd适用于旅行聊天交友相关的社交应用程序
  • 篇章五 系统性能优化——资源优化——CPU优化(2)
  • 网站建设数据库系统/郑州粒米seo顾问
  • 江山做网站/百度网站官网入口网址
  • 沧州市有建网站的吗/曼联目前积分榜
  • 现在网站用什么语言做最好/搜索seo
  • 嘉兴做网站建设的公司/重庆百度开户
  • 电子商务网站调研报告/微信搜一搜怎么做推广