Asp Core:Azure Ad Auth + 自定义 JWT + 自定义身份存储
Posted
技术标签:
【中文标题】Asp Core:Azure Ad Auth + 自定义 JWT + 自定义身份存储【英文标题】:Asp Core: Azure Ad Auth + custom JWT + custom Identity store 【发布时间】:2018-09-15 01:29:16 【问题描述】:使用 ASP.NET Core 2.0 我尝试实现以下目标:
-
通过 Azure AD(注册应用)进行身份验证
自定义 JWT 作为身份验证方案
使 Web 应用程序身份验证跨服务器/实例工作
能够保存承载以使用桌面客户端登录
拥有一个自定义身份存储来引入自定义角色、策略和其他内容。
所有这些部分都有工作示例,但在尝试将它们结合起来时,我偶然发现了一些问题。
Web Api + Azure Ad Auth 示例使用 JWT 令牌进行身份验证,但没有用于验证或创建令牌的逻辑。它也没有登录/注销的逻辑,但这似乎是合理的,它只是 Api。
这里是Web Api示例代码的快速提示:
AzureAdAuthenticationBuilderExtensions.cs
using System;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Authentication
public static class AzureAdServiceCollectionExtensions
public static AuthenticationBuilder AddAzureAdBearer(this AuthenticationBuilder builder)
=> builder.AddAzureAdBearer(_ => );
public static AuthenticationBuilder AddAzureAdBearer(this AuthenticationBuilder builder, Action<AzureAdOptions> configureOptions)
builder.Services.Configure(configureOptions);
builder.Services.AddSingleton<IConfigureOptions<JwtBearerOptions>, ConfigureAzureOptions>();
builder.AddJwtBearer();
return builder;
private class ConfigureAzureOptions: IConfigureNamedOptions<JwtBearerOptions>
private readonly AzureAdOptions _azureOptions;
public ConfigureAzureOptions(IOptions<AzureAdOptions> azureOptions)
_azureOptions = azureOptions.Value;
public void Configure(string name, JwtBearerOptions options)
options.Audience = _azureOptions.ClientId;
options.Authority = $"_azureOptions.Instance_azureOptions.TenantId";
public void Configure(JwtBearerOptions options)
Configure(Options.DefaultName, options);
Startup.cs 的摘录
public void ConfigureServices(IServiceCollection services)
services.AddAuthentication(sharedOptions =>
sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
)
.AddAzureAdBearer(options => Configuration.Bind("AzureAd", options));
services.AddMvc();
另一方面,Web 应用程序 + Azure 广告示例使用带有 cookie 的 OpenId,并且确实具有登录/注销逻辑:
AzureAdAuthenticationBuilderExtensions.cs
using System;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Authentication
public static class AzureAdAuthenticationBuilderExtensions
public static AuthenticationBuilder AddAzureAd(this AuthenticationBuilder builder)
=> builder.AddAzureAd(_ => );
public static AuthenticationBuilder AddAzureAd(this AuthenticationBuilder builder, Action<AzureAdOptions> configureOptions)
builder.Services.Configure(configureOptions);
builder.Services.AddSingleton<IConfigureOptions<OpenIdConnectOptions>, ConfigureAzureOptions>();
builder.AddOpenIdConnect();
return builder;
private class ConfigureAzureOptions : IConfigureNamedOptions<OpenIdConnectOptions>
private readonly AzureAdOptions _azureOptions;
public ConfigureAzureOptions(IOptions<AzureAdOptions> azureOptions)
_azureOptions = azureOptions.Value;
public void Configure(string name, OpenIdConnectOptions options)
options.ClientId = _azureOptions.ClientId;
options.Authority = $"_azureOptions.Instance_azureOptions.TenantId";
options.UseTokenLifetime = true;
options.CallbackPath = _azureOptions.CallbackPath;
options.RequireHttpsMetadata = false;
public void Configure(OpenIdConnectOptions options)
Configure(Options.DefaultName, options);
Startup.cs 的摘录
public void ConfigureServices(IServiceCollection services)
services.AddAuthentication(sharedOptions =>
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
)
.AddAzureAd(options => Configuration.Bind("AzureAd", options))
.AddCookie();
services.AddMvc(options =>
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
)
.AddRazorPagesOptions(options =>
options.Conventions.AllowAnonymousToFolder("/Account");
);
AccountController.cs
public class AccountController : Controller
[HttpGet]
public IActionResult SignIn()
var redirectUrl = Url.Page("/Index");
return Challenge(
new AuthenticationProperties RedirectUri = redirectUrl ,
OpenIdConnectDefaults.AuthenticationScheme
);
[HttpGet]
public IActionResult SignOut()
var callbackUrl = Url.Page("/Account/SignedOut", pageHandler: null, values: null, protocol: Request.Scheme);
return SignOut(
new AuthenticationProperties RedirectUri = callbackUrl ,
CookieAuthenticationDefaults.AuthenticationScheme, OpenIdConnectDefaults.AuthenticationScheme
);
我以某种方式合并了两种变体,但显然它不起作用。我在登录方法中当然用JwtBearerDefaults
替换了CookieAuthenticationDefault
。
Startup.cs 的摘录
public void ConfigureServices(IServiceCollection services)
services.AddEntityFrameworkSqlServer().AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddAuthentication(sharedOptions =>
sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
)
.AddAzureAd(options => Configuration.Bind("AzureAd", options))
.AddJwtBearer(options =>
options.IncludeErrorDetails = true;
options.TokenValidationParameters = new TokenValidationParameters
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "localhost",
ValidAudience = "localhost",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("test"))
;
);
services.AddMvc(options =>
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
)
.AddRazorPagesOptions(options =>
options.Conventions.AllowAnonymousToFolder("/Account");
);
我不完全了解不同的身份验证如何链接或相互依赖。据我了解,OpenId 在内部使用某种 JWT,仍然存在以下问题:
为什么 Web Api 示例只使用 JWT,而其他使用 OpenId 和 cookie? 为什么 OpenId 示例首先不使用 JWT? 自定义 JWT 是否与 OpenId 一起使用? 是否可以引入自定义身份存储,但保留 Azure AD 用于登录(和登录名)?如果您能给我一些指导,那就太好了,不需要完整的工作示例(即使这很好)
【问题讨论】:
【参考方案1】:如果您想在您的 ASP.NET Core Web 应用程序中结合使用 Cookies 和 Bearer 授权,您可以按照下面的代码 sn-p:
services.AddAuthentication(sharedOptions =>
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
)
.AddJwtBearer(jwtOptions=>
jwtOptions.IncludeErrorDetails = true;
jwtOptions.Authority = "Authority";
jwtOptions.Audience = "Audience";
jwtOptions.TokenValidationParameters = new TokenValidationParameters()
ValidIssuer = "ValidIssuer",
ValidAudience = "ValidAudience"
;
jwtOptions.Events = new JwtBearerEvents()
OnAuthenticationFailed = context =>
//TODO:
return Task.FromResult(0);
,
OnTokenValidated = context =>
//At this point, the security token has been validated successfully and a ClaimsIdentity has been created
var claimsIdentity = (ClaimsIdentity)context.Principal.Identity;
//add your custom claims here
claimsIdentity.AddClaim(new Claim("test", "helloworld!!!"));
return Task.FromResult(0);
;
)
.AddAzureAd(options => Configuration.Bind("AzureAd", options))
.AddCookie();
我注意到您添加了一个全局AuthorizeFilter
,此时您需要确保匿名操作需要使用AllowAnonymous
属性进行装饰。
services.AddMvc(options =>
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes(CookieAuthenticationDefaults.AuthenticationScheme, JwtBearerDefaults.AuthenticationScheme)
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
)
或者您可以使用Authorize
属性来装饰控制器操作,如下所示:
[Authorize(AuthenticationSchemes = "Cookies,Bearer")]
public IActionResult UserInfo()
return Json(User.Claims.Select(c => new key = c.Type, value = c.Value ));
对于 OpenID Connect 中间件,您可以修改 AzureAdAuthenticationBuilderExtensions.cs
文件下的 Configure(string name, OpenIdConnectOptions options)
方法以添加您的自定义声明(例如角色等),如下所示:
public void Configure(string name, OpenIdConnectOptions options)
options.ClientId = _azureOptions.ClientId;
options.Authority = $"_azureOptions.Instance_azureOptions.TenantId";
options.UseTokenLifetime = true;
options.CallbackPath = _azureOptions.CallbackPath;
options.RequireHttpsMetadata = false;
//the new code
options.Events = new OpenIdConnectEvents
OnTokenValidated = context =>
var claimsIdentity = (ClaimsIdentity)context.Principal.Identity;
//add your custom claims here
claimsIdentity.AddClaim(new Claim("test", "helloworld!!!"));
return Task.FromResult(0);
;
总而言之,在配置时,您可以结合 Cookies 和 Bearer 身份验证,在验证令牌后,您可以检索用户标识符并从数据库中获取其他用户信息,然后将它们附加到 ClaimsIdentity
。
对于桌面客户端,您可以利用ADAL(适用于 AD 应用 v1.0)或MSAL(适用于 AD 应用 v2.0)登录并检索 access_token
或 id_token
,然后使用它作为不记名令牌访问您的 Web 应用程序。
【讨论】:
但我仍然想提一下,如果您在 OpenId 验证后添加自定义声明并基于这些声明构建您的令牌,您将在 JWTBearer 验证后再次添加它们。但你肯定是为了一个完整的例子这样做的。以上是关于Asp Core:Azure Ad Auth + 自定义 JWT + 自定义身份存储的主要内容,如果未能解决你的问题,请参考以下文章
Azure AD 与 ASP.NET Core MVC 中的标识
Azure AD 限制整个 ASP.NET Core API
ASP.NET Core 2.2 中的 Azure AD 身份验证
ASP.NET Core 3.1 Azure AD 身份验证引发 OptionsValidationException