如何使用依赖注入在ASP.NET Core 2中使用OIDC登录到Azure AD后添加自己的声明?

问题描述:

在ASP.NET的Core 2登录到Azure的AD是相当容易的,在ConfigureServices(IServiceCollection服务)只需添加以下如何使用依赖注入在ASP.NET Core 2中使用OIDC登录到Azure AD后添加自己的声明?

// 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。我也不能通过ActivatorUtilities.CreateInstance访问ServiceCollection。

构造函数RoleClaims看起来是这样的:

public RoleClaims(IOptions<EmployeeConfiguration> configuration) 

所以,最大的问题: 这是如何工作的?我可以以某种方式在TokenValidated方法中使用依赖注入吗?我是否试图在错误的地方添加自己的主张?

在ASP.NET 2.0的核心,你可以从一个服务包含了使用:

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

我成功地在多租户场景中对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的公开二传手。

+0

TBH我不太明白什么.Success()是在这里做。当使用context.Principal.AddIdentity(new ClaimsIdentity(myNewClaims))时,似乎并不需要它。 无论如何,问题不是我想要所有的声明都是以相同的身份,而是如何通过依赖注入来访问它们。 – Terje

找到了一个方法来做到这一点。这可能不太好,但它似乎工作。

如果任何人有更好的方法来做到这一点,如果有一些这是不好的做法,我想听听它。

public class Startup 
{ 
    private IServiceCollection _serviceColletion; 
    public void ConfigureServices(IServiceCollection services) 
    { 
     _serviceColletion = 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 = _serviceColletion.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 
}