Как добавить собственные утверждения после входа в Azure AD с OIDC в ​​ASP.NET Core 2 с внедрением зависимостей?

В ASP.NET Core 2 войти в Azure AD довольно просто, в ConfigureServices (службы IServiceCollection) просто добавьте следующее

// Azure AD login
services.AddAuthentication(a =>
{
    a.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    a.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    a.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(o => o.LoginPath = new PathString("/Account/SignIn"))
.AddOpenIdConnect(o =>
{
    o.ClientId = Configuration["Authentication:AzureAd:ClientId"];
    o.ClientSecret = Configuration["Authentication:AzureAd:ClientSecret"];
    o.Authority = Configuration["Authentication:AzureAd:AADInstance"] + 
                  Configuration["Authentication:AzureAd:TenantId"];
    o.CallbackPath = Configuration["Authentication:AzureAd:CallbackPath"];
    o.ResponseType = OpenIdConnectResponseType.CodeIdToken;
    o.Events = new OpenIdConnectEvents
    {
        OnRemoteFailure = RemoteFailure,
        OnTokenValidated = TokenValidated
    };
});

и все работает нормально. Затем я могу добавить утверждения в TokenValidated, и это тоже отлично работает:

private Task TokenValidated(TokenValidatedContext context)
{
    var claims = new List<Claim>();
    var claim = new Claim(ClaimTypes.Role, "Test", ClaimValueTypes.String, "Issuername")
    context.Principal.AddIdentity(new ClaimsIdentity(claims));
    return Task.FromResult(0);
}

Однако это никогда не бывает так просто. Требуемые утверждения зависят от внешних вызовов службы, а адрес сохраняется в конфигурации.

В ConfigureServices у меня также есть различные классы, добавленные для внедрения зависимостей, которые отлично работают для контроллеров.

services.AddTransient<IRoleClaims, RoleClaims>();

Этот RoleClaims - это класс, который я хочу вызвать из метода TokenValidated, но, насколько я понимаю, я не могу использовать здесь DI. Я также не могу получить доступ к ServiceCollection, чтобы получить его через ActivatorUtilities.CreateInstance.

Конструктор RoleClaims выглядит так:

public RoleClaims(IOptions<EmployeeConfiguration> configuration)

Итак, большой вопрос: как это должно работать? Могу ли я как-то использовать внедрение зависимостей в методе TokenValidated? Я пытаюсь добавить свои претензии не в то место?


person Terje    schedule 17.08.2017    source источник


Ответы (3)


В ASP.NET Core 2.0 вы можете получить службу из контейнера, используя:

private async Task TokenValidated(TokenValidatedContext context)
{
  var widget = ctx.HttpContext.RequestServices.GetRequiredService<Widget>();
  ...
}
person Cocowalla    schedule 18.01.2018

Мне удалось пройти аутентификацию на IdentityServer4 в сценарии с несколькими арендаторами, когда мне нужно было вводить учетные данные клиента и другие данные для каждого запроса. Вот почему я также «испортил» свой код с пользовательским OpenIdConnectEvents.

OnTokenValidated func - это правильное место. Цель состоит в том, чтобы присвоить значение TokenValidatedContext.Result (чьим установщиком, к сожалению, является protected). Однако вы можете вызвать метод .Success(), который устанавливает свойство в соответствии с тем, что доступно:

Task TokenValidated(TokenValidatedContext context)
{
    //[...] gathering claims   
    var ci = new ClaimsIdentity(context.Scheme.Name, "name", "role");
    ci.AddClaims(my_previously_gathered_Claims);
    context.Principal = new ClaimsPrincipal(ci);  
    // .Success() uses 
    // 1. the principal just set above  
    // 2. the context properties
    // 3. the context scheme
    // to create the underlying ticket                       
    context.Success();
}

Это должно делать свое дело.

Я лично предпочел бы публичный сеттер для .Result.

person Cristian Merighi    schedule 18.08.2017
comment
Tbh Я не совсем понимаю, что здесь делает .Success (). Кажется, это не нужно при использовании context.Principal.AddIdentity (new ClaimsIdentity (myNewClaims)) по крайней мере. В любом случае проблема заключалась не в том, что я хотел, чтобы все утверждения были в одном удостоверении, а в том, как получить к ним доступ через внедрение зависимостей. - person Terje; 25.08.2017

Нашел способ это сделать. Может, это и некрасиво, но вроде работает.

Если у кого-то есть лучший способ сделать это, или если что-то из этого является плохой практикой, я хотел бы это услышать.

public class Startup
{
    private IServiceCollection _serviceCollection;
    public void ConfigureServices(IServiceCollection services)
    {
        _serviceCollection = services; // Hacky way to access services in other methods :s
        // services.AddStuff() down here, including the AzureAD OIDC
    }
    private async Task TokenValidated(TokenValidatedContext context)
    {
        IRoleClaims roleClaims; // My class for reading from services/database
                                // and create claims

        // This is the magic DI workaround I was looking for
        var scopeFactory = _serviceCollection.BuildServiceProvider()
                           .GetRequiredService<IServiceScopeFactory>();
        using (var scope = scopeFactory.CreateScope())
        {
            var provider = scope.ServiceProvider;
            roleClaims = provider.GetRequiredService<IRoleClaims>();
        }

        // Getting the ObjectID for the user from AzureAD
        var objectId = context.SecurityToken.Claims
            .Where(o => o.Type == "oid")
            .Select(o => o.Value)
            .SingleOrDefault();

        var claims = await roleClaims.CreateRoleClaimsForUser(objectId);
        context.Principal.AddIdentity(new ClaimsIdentity(claims));
    }
    // Rest of the methods not shown
}
person Terje    schedule 25.08.2017