Bitzsoft.Integrations.AzureAD
1.0.0-alpha.10
dotnet add package Bitzsoft.Integrations.AzureAD --version 1.0.0-alpha.10
NuGet\Install-Package Bitzsoft.Integrations.AzureAD -Version 1.0.0-alpha.10
<PackageReference Include="Bitzsoft.Integrations.AzureAD" Version="1.0.0-alpha.10" />
<PackageVersion Include="Bitzsoft.Integrations.AzureAD" Version="1.0.0-alpha.10" />
<PackageReference Include="Bitzsoft.Integrations.AzureAD" />
paket add Bitzsoft.Integrations.AzureAD --version 1.0.0-alpha.10
#r "nuget: Bitzsoft.Integrations.AzureAD, 1.0.0-alpha.10"
#:package Bitzsoft.Integrations.AzureAD@1.0.0-alpha.10
#addin nuget:?package=Bitzsoft.Integrations.AzureAD&version=1.0.0-alpha.10&prerelease
#tool nuget:?package=Bitzsoft.Integrations.AzureAD&version=1.0.0-alpha.10&prerelease
Bitzsoft.Integrations.AzureAD
Azure AD / Microsoft Entra ID 集成客户端 — OAuth 2.0 PKCE 流程、OIDC Token 验证、Graph API 组查询、用户自动供应。
功能特性
- OAuth 2.0 PKCE 授权码流程:完整实现 Auth Code Flow + PKCE,支持 MVC / Web App 场景
- OIDC Token 验证:区分 ID Token(登录验证)和 Access Token(API 鉴权)两类场景
- ASP.NET Core JwtBearer 集成:一键注册后
[Authorize]直接生效 - Graph API 组查询:通过共享 Graph/REST transport 与 Client Credentials 应用身份查询用户直接所属组 + 角色映射
- 用户自动供应:外部登录绑定 + 邮箱匹配
- JWKS 自动轮换:基于
ConfigurationManager自动刷新签名密钥 - 登录与 Graph 租户解耦:登录可使用
common/organizations, Graph 应用身份必须绑定具体目录租户 - 国家云/代理端点:Graph v1.0 服务根、身份实例和 resource audience 均可配置
- 第三方请求日志:交互式 OAuth/OIDC 默认挂载 RequestLogging; Graph 共享 HTTP 管道可由宿主统一挂载
支持 net5.0、net8.0 与 net10.0。
安装
.NET CLI
dotnet add package Bitzsoft.Integrations.AzureAD
PackageReference
<PackageReference Include="Bitzsoft.Integrations.AzureAD" Version="1.0.0" />
配置
appsettings.json
{
"AzureAD": {
"Enabled": true,
"Instance": "https://login.microsoftonline.com/",
"TenantId": "common",
"GraphTenantId": "your-concrete-directory-tenant-id",
"GraphApiBaseUrl": "https://graph.microsoft.com/v1.0",
"ClientId": "your-client-id",
"ClientSecret": "your-client-secret",
"CallbackPath": "/api/callback/azuread",
"Audience": "api://your-client-id",
"AllowedTenantIds": [
"00000000-0000-0000-0000-000000000000"
],
"TokenClockSkew": "00:05:00",
"Scopes": [ "openid", "profile", "email" ]
}
}
| 配置项 | 说明 | 默认值 |
|---|---|---|
Enabled |
是否启用 | false |
Instance |
Azure AD 实例地址 | https://login.microsoftonline.com/ |
TenantId |
租户 ID,多租户使用 common |
common |
GraphTenantId |
Graph 应用身份使用的具体租户;为空时回退到 TenantId |
空 |
GraphApiBaseUrl |
Microsoft Graph v1.0 服务根 | https://graph.microsoft.com/v1.0 |
GraphResourceAudience |
自定义代理的 OAuth resource audience | Graph 服务根的 origin |
GraphRequestTimeout |
Graph 单次操作总超时 | 00:00:30 |
ClientId |
应用程序(客户端)ID | -- |
ClientSecret |
客户端密钥 | -- |
CallbackPath |
OAuth 回调路径 | /api/callback/azuread |
Audience |
Access Token 受众(SPA 场景必填) | 默认 fallback 为 ClientId |
AllowedTenantIds |
允许登录/调用 API 的租户 GUID 白名单;空表示接受 authority 范围内任意已验证租户 | [] |
TokenClockSkew |
Token 生命周期允许的时钟偏差,范围 0~15 分钟 | 00:05:00 |
Scopes |
OAuth 请求的 Scope | ["openid","profile","email"] |
注册服务
核心注册(必选)
using Bitzsoft.Integrations.AzureAD.DependencyInjection;
// 注册核心服务(OAuth + Token 验证)
services.AddBitzsoftAzureAD(options =>
{
options.Instance = "https://login.microsoftonline.com/";
options.TenantId = "common";
options.GraphTenantId = "your-concrete-directory-tenant-id";
options.ClientId = "your-client-id";
options.ClientSecret = "your-client-secret";
options.Audience = "api://your-client-id";
options.AllowedTenantIds =
[
"00000000-0000-0000-0000-000000000000"
];
});
// 或从 IConfiguration 绑定
services.AddBitzsoftAzureAD(configuration.GetSection("AzureAD").Bind);
ASP.NET Core JwtBearer 集成(可选)
// 注册后 [Authorize] 直接生效,User.Identity 即为 AAD 用户
services.AddBitzsoftAzureADJwtBearer();
JwtBearer 与手工 ValidateIdTokenAsync / ValidateAccessTokenAsync 使用同一套
严格验证策略:签名、audience、生命周期、tid、租户化 issuer 和签名密钥
issuer 均必须通过。MapInboundClaims 固定为 false,业务代码可直接读取
tid、oid 等原始 Entra claim。
Graph API 组查询(可选)
services.AddBitzsoftAzureADGraph()
.WithGroupRoleMapping<SqlGroupRoleMappingRepository>();
Graph API 使用 Client Credentials(应用身份)。当前 API 读取用户的直接
组成员关系,不包含嵌套组传递关系;应用需授予满足该接口的 Application
permission(通常为 Directory.Read.All)并完成管理员同意。不能把
common、organizations 或 consumers 用作应用身份目录租户。
中国版示例:
options.Instance = "https://login.chinacloudapi.cn/";
options.GraphTenantId = "your-concrete-directory-tenant-id";
options.GraphApiBaseUrl =
"https://microsoftgraph.chinacloudapi.cn/v1.0";
用户自动供应(可选)
services.AddBitzsoftAzureADProvisioning()
.WithExternalLogin<SqlExternalLoginRepository>()
.WithUserLookup<SqlUserLookupRepository>();
第三方请求日志
内置
Bitzsoft.Integrations.RequestLogging
出站请求记录管道,默认使用 NullRequestLogStore 不持久化。交互式
OAuth/OIDC 管道由 AddBitzsoftAzureAD 自动挂载;Graph Adapter 复用共享
REST HTTP 管道,不再重复挂载一套 handler。
using Bitzsoft.Integrations.Rest;
// ① 默认:启用记录管道但不持久化(日志丢弃)
services.AddBitzsoftAzureAD(options => { /* ... */ });
// ② 持久化:宿主注册 IRequestLogStore 实现
services.AddRequestLogging<MyRequestLogStore>(opts =>
{
opts.MaxInMemoryBodyBytes = 64 * 1024; // 仅控制内存/加密临时文件切换,不截断正文
opts.SensitiveFields.Add("mySecret");
});
services.AddBitzsoftAzureAD(options => { /* ... */ });
// ③ 如需记录 Graph,共享 REST 管道只挂载一次
services.AddHttpClient(RestConstants.HttpClientName)
.AddRequestLogging("MicrosoftGraph");
services.AddBitzsoftAzureADGraph();
MaxLoggedBodyLength 默认是 null,不会强制限制日志正文大小;兆级第三方
response 仍可完整保存。只有宿主显式设置该选项时才截断。
OAuth Token 交换是例外:授权码、PKCE verifier、Client Secret 与返回的各类
Token 整段都属于凭据,因此连接器会显式调用
SuppressRequestAndResponseBodiesFromLogging(),只保留端点、状态码、耗时等
审计元数据。这个安全分类不会改变其他请求的正文记录策略,更不会给普通
response 增加强制大小上限。
使用示例
MVC / Web App — OAuth 登录流程
using Bitzsoft.Integrations.AzureAD.Interfaces;
public class AccountController : Controller
{
private readonly IAzureADClient _aadClient;
public AccountController(IAzureADClient aadClient)
{
_aadClient = aadClient;
}
// Step 1: 生成授权 URL
public IActionResult Login()
{
var codeVerifier = _aadClient.GenerateCodeVerifier();
var codeChallenge = _aadClient.GenerateCodeChallenge(codeVerifier);
var nonce = _aadClient.GenerateNonce();
var state = Guid.NewGuid().ToString("N");
HttpContext.Session.SetString("pkce_verifier", codeVerifier);
HttpContext.Session.SetString("nonce", nonce);
HttpContext.Session.SetString("state", state);
var authUrl = _aadClient.GetAuthorizationUrl(
Url.Action("Callback", null, null, Request.Scheme),
state, codeChallenge, nonce);
return Redirect(authUrl);
}
// Step 2: 处理回调
public async Task<IActionResult> Callback(string code, string state)
{
var codeVerifier = HttpContext.Session.GetString("pkce_verifier");
var expectedNonce = HttpContext.Session.GetString("nonce");
var tokenResponse = await _aadClient.ExchangeCodeForTokenAsync(
code, Url.Action("Callback", null, null, Request.Scheme), codeVerifier!);
if (tokenResponse?.IdToken is null)
return BadRequest("Token exchange failed");
var userInfo = await _aadClient.ValidateIdTokenAsync(
tokenResponse.IdToken, expectedNonce);
if (userInfo is null)
return BadRequest("ID Token validation failed");
// 使用 userInfo 登录系统
return RedirectToAction("Index", "Home");
}
}
SPA / Mobile App — Access Token 验证
using Bitzsoft.Integrations.AzureAD.Interfaces;
[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
private readonly IAzureADClient _aadClient;
public AuthController(IAzureADClient aadClient)
{
_aadClient = aadClient;
}
[HttpPost("validate")]
public async Task<IActionResult> ValidateToken()
{
var accessToken = Request.Headers["Authorization"]
.ToString().Replace("Bearer ", "");
var userInfo = await _aadClient.ValidateAccessTokenAsync(accessToken);
if (userInfo is null)
return Unauthorized();
return Ok(userInfo);
}
}
使用 [Authorize](需注册 JwtBearer 扩展)
using Microsoft.AspNetCore.Authorization;
[ApiController]
[Route("api/[controller]")]
public class ApiController : ControllerBase
{
[HttpGet("profile")]
[Authorize(AuthenticationSchemes = "AzureADJwtBearer")]
public IActionResult GetProfile()
{
var oid = User.FindFirst("oid")?.Value;
var name = User.FindFirst("name")?.Value;
return Ok(new { ObjectId = oid, Name = name });
}
}
Graph API — 查询用户组并匹配角色
using Bitzsoft.Integrations.AzureAD.Interfaces;
public class RoleService
{
private readonly IAzureADGraphClient _graphClient;
public RoleService(IAzureADGraphClient graphClient)
{
_graphClient = graphClient;
}
public async Task<List<string>> GetUserRolesAsync(
string tenantId, string userObjectId)
{
return await _graphClient.GetMatchedRoleIdsAsync(tenantId, userObjectId);
}
}
Graph 分页过程中任何一页失败都会抛出 AzureADException,不会把已读取的
部分组伪装成成功结果;异常不包含供应商响应正文、令牌或用户标识。
核心类型一览
| 类型 | 说明 |
|---|---|
IAzureADClient |
核心 OAuth + Token 验证接口 |
IAzureADGraphClient |
Graph API 直接组查询兼容接口(Client Credentials) |
IAzureADProvisioningClient |
用户自动供应接口 |
AzureADOptions |
配置模型 |
AADTokenResponse |
OAuth 令牌响应 |
AADUserInfo |
用户信息 |
AADGroupInfo |
组信息 |
GroupRoleMapping |
组-角色映射 |
ProvisioningResult |
供应结果 |
AzureADException |
自定义异常 |
ID Token vs Access Token
| 场景 | Token 类型 | 验证方法 | Audience |
|---|---|---|---|
| MVC/Web App 登录 | ID Token | ValidateIdTokenAsync |
ClientId |
| SPA/Mobile App 调用 API | Access Token | ValidateAccessTokenAsync |
api://xxx |
| ASP.NET Core [Authorize] | Access Token | 自动(中间件) | api://xxx |
多租户说明
当 TenantId 设为 common 或 organizations 时:
- issuer 验证始终开启;验证器要求 Token 恰好包含一个规范 GUID 格式的
tid,把 OIDC metadata 中的{tenantid}替换为该值后与 Token issuer 做精确匹配; - issuer 路径中的租户段必须与
tid一致,并同时校验 audience、签名、 生命周期及签名密钥 issuer;支持的较新目标框架还会校验签名密钥云实例; - 建议生产 ToB 应用配置
AllowedTenantIds白名单。若留空,技术上允许 authority 范围内任意通过密码学验证的租户,宿主仍必须在业务层执行租户 入驻、停用、授权范围和数据隔离检查; state与 nonce 由宿主保存为一次性会话值并在回调时核对。连接器对 nonce 使用常量时间比较,但不能替代宿主的 state/nonce 存储和消费逻辑。
授权 URL、Token 交换和登出入口会拒绝非 HTTPS 重定向(loopback HTTP
除外)、不合规 PKCE、空值、控制字符和超长 Token。无 Client Secret 的
Public Client + PKCE 场景不会发送空的 client_secret 字段。
依赖
- Bitzsoft.Integrations.Compatibility — 基础工具库
- Bitzsoft.Integrations.MicrosoftGraph — 共享 Graph v1.0 transport
- Bitzsoft.Integrations.Rest — OAuth、HTTP 生命周期与结构化错误底座
Microsoft.Extensions.Http— IHttpClientFactoryMicrosoft.Extensions.Options— 选项模式Microsoft.IdentityModel.Protocols.OpenIdConnect— OIDC 协议Microsoft.IdentityModel.Validators— Entra 签名密钥 issuer / 云实例校验System.IdentityModel.Tokens.Jwt— JWT Token 处理Microsoft.AspNetCore.Authentication.JwtBearer— ASP.NET Core JwtBearer 集成(可选)
相关包
- Bitzsoft.Integrations.MFA — 多因素认证集成(TOTP、FIDO2、短信/邮件验证码)
- Bitzsoft.Integrations.Ldap — LDAP 目录服务集成
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 is compatible. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Bitzsoft.Integrations.Compatibility (>= 1.0.0-alpha.10)
- Bitzsoft.Integrations.Core (>= 1.0.0-alpha.10)
- Bitzsoft.Integrations.MicrosoftGraph (>= 1.0.0-alpha.10)
- Bitzsoft.Integrations.RequestLogging (>= 1.0.0-alpha.10)
- Bitzsoft.Integrations.Rest (>= 1.0.0-alpha.10)
- Microsoft.AspNetCore.Authentication.JwtBearer (>= 8.0.18)
- Microsoft.Extensions.Http (>= 10.0.10)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.10)
- Microsoft.Extensions.Options (>= 10.0.10)
- Microsoft.IdentityModel.Protocols.OpenIdConnect (>= 8.12.0)
- Microsoft.IdentityModel.Validators (>= 8.12.0)
- System.IdentityModel.Tokens.Jwt (>= 8.12.0)
-
net5.0
- Bitzsoft.Integrations.Compatibility (>= 1.0.0-alpha.10)
- Bitzsoft.Integrations.Core (>= 1.0.0-alpha.10)
- Bitzsoft.Integrations.MicrosoftGraph (>= 1.0.0-alpha.10)
- Bitzsoft.Integrations.RequestLogging (>= 1.0.0-alpha.10)
- Bitzsoft.Integrations.Rest (>= 1.0.0-alpha.10)
- Microsoft.AspNetCore.Authentication.JwtBearer (>= 5.0.17)
- Microsoft.Extensions.Http (>= 5.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 5.0.0)
- Microsoft.Extensions.Options (>= 5.0.0)
- Microsoft.IdentityModel.Protocols.OpenIdConnect (>= 6.35.0)
- Microsoft.IdentityModel.Validators (>= 6.35.0)
- System.IdentityModel.Tokens.Jwt (>= 6.35.0)
-
net8.0
- Bitzsoft.Integrations.Compatibility (>= 1.0.0-alpha.10)
- Bitzsoft.Integrations.Core (>= 1.0.0-alpha.10)
- Bitzsoft.Integrations.MicrosoftGraph (>= 1.0.0-alpha.10)
- Bitzsoft.Integrations.RequestLogging (>= 1.0.0-alpha.10)
- Bitzsoft.Integrations.Rest (>= 1.0.0-alpha.10)
- Microsoft.AspNetCore.Authentication.JwtBearer (>= 8.0.18)
- Microsoft.Extensions.Http (>= 10.0.10)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.10)
- Microsoft.Extensions.Options (>= 10.0.10)
- Microsoft.IdentityModel.Protocols.OpenIdConnect (>= 8.12.0)
- Microsoft.IdentityModel.Validators (>= 8.12.0)
- System.IdentityModel.Tokens.Jwt (>= 8.12.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0-alpha.10 | 33 | 7/26/2026 |
| 1.0.0-alpha.9 | 55 | 7/12/2026 |
| 1.0.0-alpha.8 | 162 | 7/1/2026 |
| 1.0.0-alpha.7 | 324 | 6/16/2026 |
| 1.0.0-alpha.6 | 67 | 6/16/2026 |
| 1.0.0-alpha.5 | 61 | 6/14/2026 |
| 1.0.0-alpha.4 | 56 | 7/1/2026 |
| 1.0.0-alpha.3 | 62 | 6/7/2026 |
| 1.0.0-alpha.2 | 64 | 5/29/2026 |