我已使用this示例为具有MSAL v2库的Azure AD配置OpendID身份验证。它使用AzureAdAuthenticationBuilderExtensions类配置OpenId连接事件(如下所示的代码)。
我想在这些事件中访问我的数据库(EF Core)以检查tenantId并添加一些自定义用户声明。问题在于注入的数据库上下文(services.AddDbContext())是作用域服务,不能在Configure方法中调用。
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;
}
public class ConfigureAzureOptions: IConfigureNamedOptions<OpenIdConnectOptions>
{
private readonly AzureAdOptions _azureOptions;
public AzureAdOptions GetAzureAdOptions() => _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;
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
//var allScopes = $"{_azureOptions.Scopes} {_azureOptions.GraphScopes}".Split(new[] {' '});
var allScopes = $"{_azureOptions.Scopes} https://graph.microsoft.com/.default".Split(new[] { ' ' }); ;
foreach (var scope in allScopes) { options.Scope.Add(scope); }
options.TokenValidationParameters = new TokenValidationParameters
{
// Ensure that User.Identity.Name is set correctly after login
NameClaimType = "name",
// Instead of using the default validation (validating against a single issuer value, as we do in line of business apps),
// we inject our own multitenant validation logic
ValidateIssuer = false,
// If the app is meant to be accessed by entire organizations, add your issuer validation logic here.
//IssuerValidator = (issuer, securityToken, validationParameters) => {
// if (myIssuerValidationLogic(issuer)) return issuer;
//}
};
options.Events = new OpenIdConnectEvents
{
OnTicketReceived = context =>
{
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
context.Response.Redirect("/Home/Error");
Console.WriteLine(context.Exception.Message);
context.HandleResponse(); // Suppress the exception
return Task.CompletedTask;
},
OnAuthorizationCodeReceived = async (context) =>
{
var code = context.ProtocolMessage.Code;
var identifier = context.Principal.Claims.FirstOrDefault(c => c.Type.Contains("objectidentifier")).Value;
var memoryCache = context.HttpContext.RequestServices.GetRequiredService<IMemoryCache>();
//var graphScopes = _azureOptions.GraphScopes.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
var graphScopes = new string[] { "https://graph.microsoft.com/.default" };
var cca = new ConfidentialClientApplication(
_azureOptions.ClientId,
$"{_azureOptions.Instance}{_azureOptions.TenantId}",
_azureOptions.BaseUrl + _azureOptions.CallbackPath,
new ClientCredential(_azureOptions.ClientSecret),
new SessionTokenCache(identifier, memoryCache).GetCacheInstance(),
null);
//var result = await cca.AcquireTokenByAuthorizationCodeAsync(code, graphScopes);
var result = await cca.AcquireTokenByAuthorizationCodeAsync(code, graphScopes);
// Check whether the login is from the MSA tenant.
// The sample uses this attribute to disable UI buttons for unsupported operations when the user is logged in with an MSA account.
var currentTenantId = context.Principal.Claims.FirstOrDefault(c => c.Type.Contains("tenantid")).Value;
if (currentTenantId == "9188040d-6c67-4c5b-b112-36a304b66dad")
{
// MSA (Microsoft Account) is used to log in
}
context.HandleCodeRedemption(result.AccessToken, result.IdToken);
},
// If your application needs to do authenticate single users, add your user validation below.
//OnTokenValidated = context =>
//{
// return myUserValidationLogic(context.Ticket.Principal);
//}
};
}
public void Configure(OpenIdConnectOptions options)
{
Configure(Options.DefaultName, options);
}
}
}
答案 0 :(得分:1)
要访问DbContext
,可以尝试HttpContext.RequestServices
。
public void Configure(string name, OpenIdConnectOptions options)
{
//your code
options.Events = new OpenIdConnectEvents
{
OnTicketReceived = context =>
{
var db = context.HttpContext.RequestServices.GetRequiredService<ApplicationDbContext>();
// If your authentication logic is based on users then add your logic here
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
var db = context.HttpContext.RequestServices.GetRequiredService<ApplicationDbContext>();
context.Response.Redirect("/Home/Error");
context.HandleResponse(); // Suppress the exception
return Task.CompletedTask;
},
OnAuthorizationCodeReceived = async (context) =>
{
var db = context.HttpContext.RequestServices.GetRequiredService<ApplicationDbContext>();
var code = context.ProtocolMessage.Code;
var identifier = context.Principal.FindFirst(Startup.ObjectIdentifierType).Value;
var memoryCache = context.HttpContext.RequestServices.GetRequiredService<IMemoryCache>();
var graphScopes = _azureOptions.GraphScopes.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
var cca = new ConfidentialClientApplication(
_azureOptions.ClientId,
_azureOptions.BaseUrl + _azureOptions.CallbackPath,
new ClientCredential(_azureOptions.ClientSecret),
new SessionTokenCache(identifier, memoryCache).GetCacheInstance(),
null);
var result = await cca.AcquireTokenByAuthorizationCodeAsync(code, graphScopes);
// Check whether the login is from the MSA tenant.
// The sample uses this attribute to disable UI buttons for unsupported operations when the user is logged in with an MSA account.
var currentTenantId = context.Principal.FindFirst(Startup.TenantIdType).Value;
if (currentTenantId == "9188040d-6c67-4c5b-b112-36a304b66dad")
{
// MSA (Microsoft Account) is used to log in
}
context.HandleCodeRedemption(result.AccessToken, result.IdToken);
},
// If your application needs to do authenticate single users, add your user validation below.
//OnTokenValidated = context =>
//{
// return myUserValidationLogic(context.Ticket.Principal);
//}
};
}
答案 1 :(得分:0)
您可以创建一个新方法来配置服务,而不是在Configure方法中调用它,如您所引用的同一示例的注释中所示。
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContextPool<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
}
此Github issue中的用户似乎遇到了与您类似的问题。请查看此处的对话是否有帮助。