如何为.NET API端点同时实现JWT与Azure AD双认证授权?
让.NET API同时支持自定义JWT和Azure AD两种授权方案
我来帮你搞定这个问题——要让你的.NET后端API同时支持用户名密码生成的自定义JWT和Azure AD登录两种授权方式,核心是在ASP.NET Core中配置多个认证方案,并让API端点接受任意一种有效的令牌。下面是完整的实现步骤:
一、核心原理
ASP.NET Core的认证系统支持同时注册多种认证方案(比如JwtBearer、AzureADBearer),你可以:
- 设置全局默认的认证/挑战方案
- 为单个API端点指定允许的认证方案
- 自定义授权策略,要求满足任意一种认证方式
二、具体实现步骤
1. 安装必要的NuGet包
首先需要添加Azure AD认证相关的官方包,推荐使用Microsoft.Identity.Web(它封装了Azure AD认证的细节,更易用):
Install-Package Microsoft.Identity.Web
2. 修改Program.cs配置
下面是更新后的完整代码,我标注了关键改动点:
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authentication.AzureAD.UI; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; using System.Text; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddCors(p => p.AddPolicy("corsapp", builder => { builder.WithOrigins("http://localhost:8082").AllowAnyMethod().AllowAnyHeader(); })); builder.Services.AddControllers().AddJsonOptions(options => { options.JsonSerializerOptions.PropertyNamingPolicy = null; }); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "BaseWebApi", Version = "v1" }); // 1. 添加自定义JWT的认证定义 c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { Description = "自定义JWT授权格式: Bearer {token}", Name = "Authorization", In = ParameterLocation.Header, Type = SecuritySchemeType.ApiKey, Scheme = "Bearer" }); // 2. 添加Azure AD的OAuth2认证定义 c.AddSecurityDefinition("AzureAD", new OpenApiSecurityScheme { Type = SecuritySchemeType.OAuth2, Flows = new OpenApiOAuthFlows { AuthorizationCode = new OpenApiOAuthFlow { AuthorizationUrl = new Uri($"https://login.microsoftonline.com/{builder.Configuration["AzureAd:TenantId"]}/oauth2/v2.0/authorize"), TokenUrl = new Uri($"https://login.microsoftonline.com/{builder.Configuration["AzureAd:TenantId"]}/oauth2/v2.0/token"), Scopes = new Dictionary<string, string> { { builder.Configuration["AzureAd:Scope"], "API访问权限" } } } } }); // 3. 添加安全要求,允许任意一种认证方式 c.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } }, new string[] {} }, { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "AzureAD" } }, new[] { builder.Configuration["AzureAd:Scope"] } } }); }); // 配置多种认证方案 builder.Services.AddAuthentication(options => { // 可选:设置默认的认证方案,如果不设置,需要在端点上指定[Authorize(AuthenticationSchemes = "...")] // options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; // options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) // 1. 保留原来的自定义JWT认证(指定方案名称为"CustomJwt") .AddJwtBearer("CustomJwt", options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = builder.Configuration["Jwt:Issuer"], ValidAudience = builder.Configuration["Jwt:Audience"], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:key"])) }; }) // 2. 添加Azure AD认证方案(指定方案名称为"AzureAD") .AddAzureADBearer("AzureAD", options => { builder.Configuration.Bind("AzureAd", options); }); // 可选:创建授权策略,允许任意一种认证方案通过 builder.Services.AddAuthorization(options => { options.AddPolicy("AllowEitherAuth", policy => { policy.AuthenticationSchemes.Add("CustomJwt"); policy.AuthenticationSchemes.Add("AzureAD"); policy.RequireAuthenticatedUser(); }); }); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "DemoJWTToken v1"); // 配置Swagger UI支持Azure AD登录 c.OAuthClientId(builder.Configuration["AzureAd:ClientId"]); c.OAuthScopeSeparator(" "); c.OAuthUseBasicAuthenticationWithAccessCodeGrant(); }); } app.UseHttpsRedirection(); app.UseCors("corsapp"); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.Run();
3. 配置appsettings.json
需要在配置文件中添加Azure AD的相关参数:
{ "Jwt": { "Issuer": "你的自定义JWT发行者", "Audience": "你的自定义JWT受众", "Key": "你的自定义JWT密钥" }, "AzureAd": { "Instance": "https://login.microsoftonline.com/", "TenantId": "你的Azure AD租户ID", "ClientId": "你的API在Azure AD中的应用ID", "Scope": "api://<你的API应用ID>/access_as_user" // 替换为你的API的访问范围 } }
4. 保护API端点的方式
你有两种灵活的方式来保护API:
方式一:使用全局策略(推荐)
在控制器或方法上应用自定义策略,这样端点会自动接受两种认证方式的令牌:
[ApiController] [Route("[controller]")] [Authorize(Policy = "AllowEitherAuth")] public class WeatherForecastController : ControllerBase { // ... 你的API方法逻辑 }
方式二:直接指定允许的认证方案
在[Authorize]属性中明确列出支持的方案:
[Authorize(AuthenticationSchemes = "CustomJwt,AzureAD")] public IActionResult GetUserData() { return Ok(HttpContext.User.Identity.Name); }
三、测试验证
- 自定义JWT测试:通过用户名密码登录获取JWT,在请求头中添加
Authorization: Bearer <你的自定义JWT>,调用API验证是否能正常访问。 - Azure AD测试:在Swagger UI中选择"AzureAD"认证,完成登录后获取令牌调用API;或者在React应用中通过Azure AD登录获取访问令牌,添加到请求头后调用API。
关键注意事项
- 确保Azure AD中已正确注册API应用,并配置了正确的访问范围和客户端权限。
- 两种认证方案的令牌验证逻辑是独立的,各自校验自身令牌的有效性。
- 如果需要区分用户的登录方式,可以在控制器中通过
HttpContext.User.Identity.AuthenticationType来判断(值为"CustomJwt"或"AzureAD")。
内容的提问来源于stack exchange,提问作者Avanish Srivastava
相关产品推荐
相关产品推荐

