如何获取 IdentityServer4 本身的自定义声明
How to get custom claims for IdentityServer4 Itself
我 运行 IdentityServer4 托管在类似于 https://github.com/IdentityServer/IdentityServer4/tree/master/samples/Quickstarts/6_AspNetIdentity/src/IdentityServerAspNetIdentity 的 MVC 应用程序中。
这个 IdentityServer 主机在 ConfigureServices 方法的底部附近公开了一个这样的配置文件服务。
services.AddTransient<IProfileService, ProfileService>();
从我看过的所有示例和快速入门中,我没有看到 IdentityServer MVC 主机本身可以在哪里访问配置文件数据。这意味着 IDServ MVC 主机是其自身的客户端,可以访问声明数据。我见过 IDServ 将 OpenIdConnect 添加为外部提供者的示例,但似乎 MVC 应用程序将自己列为外部提供者以便我可以获得 ProfileService 声明数据。
我的 Startup.cs(主机 IDServ MVC 应用程序)看起来像这样
public class Startup
{
public Startup(IConfiguration configuration, IHostingEnvironment env)
{
Configuration = configuration;
HostingEnvironment = env;
}
... removed for brevity
public void ConfigureServices(IServiceCollection services)
{
var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
var connectionString = Configuration.GetConnectionString("connString");
services.AddControllersWithViews();
// configures IIS out-of-proc settings (see https://github.com/aspnet/AspNetCore/issues/14882)
services.Configure<IISOptions>(iis =>
{
iis.AuthenticationDisplayName = "Windows";
iis.AutomaticAuthentication = false;
});
// configures IIS in-proc settings
services.Configure<IISServerOptions>(iis =>
{
iis.AuthenticationDisplayName = "Windows";
iis.AutomaticAuthentication = false;
});
services.AddDbContext<AuthDbContext>(b =>
b.UseSqlServer(connectionString,
sqlOptions =>
{
sqlOptions.MigrationsAssembly(typeof(AuthDbContext).GetTypeInfo().Assembly.GetName().Name);
sqlOptions.EnableRetryOnFailure(5, TimeSpan.FromSeconds(1), null);
})
);
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<AuthDbContext>()
.AddDefaultTokenProviders();
services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
})
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = b => b.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly));
})
.AddOperationalStore(options =>
{
options.ConfigureDbContext = b => b.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly));
options.EnableTokenCleanup = true;
})
.AddAspNetIdentity<ApplicationUser>()
.AddSigningAuthority(HostingEnvironment, Configuration)
.AddProfileService<ProfileService>();
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies");
services.AddTransient<IProfileService, ProfileService>();
services.AddTransient<AzureTableStorageLoggerMiddleware>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory, IHttpContextAccessor accessor)
{
app.UseMiddleware<AzureTableStorageLoggerMiddleware>();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthorization();
loggerFactory.AddTableStorage(env.EnvironmentName + "Auth", Configuration["AzureStorageConnectionString"], accessor);
app.UseMiddleware<AzureTableStorageLoggerMiddleware>();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
}
正如您在上面看到的,我没有在其自身上使用 .AddOpenIdConnect,我想知道是否需要在主机本身上添加它,以便我可以像这样在主机 IDServ 应用程序上获取配置文件服务声明数据。 ..
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.Authority = "https://localhost:44378/"; //seems silly to have it point to it's own host
options.RequireHttpsMetadata = false;
options.GetClaimsFromUserInfoEndpoint = true;
options.ClientId = "idserv";
options.ClientSecret = "<<>>";
options.ResponseType = "code id_token token";
options.SaveTokens = true;
});
好的一面是,使用 .AddOpenIdConnect() 中间件方法时,完全独立的 MVC 客户端确实会获取 ProfileService 声明数据,而不是主机。
谢谢
- 正如 IdentityServer 的文档所说:
You can provide a callback to transform the claims of the incoming token after validation. Either use the helper method, e.g.:
services.AddLocalApiAuthentication(principal =>
{
principal.Identities.First().AddClaim(new Claim("additional_claim", "additional_value"));
return Task.FromResult(principal);
});
中阅读完整指南
- 您可以编写新的中间件并加载用户声明。
public class ClaimsMiddleware
{
private readonly RequestDelegate _next;
public ClaimsMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext httpContext, UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager)
{
if (httpContext.User != null && httpContext.User.Identity.IsAuthenticated)
{
var sub = httpContext.User.Claims.SingleOrDefault(c => c.Type == JwtClaimTypes.Subject);
if (sub != null)
{
var user = await userManager.FindByIdAsync(sub.Value);
if (user != null)
{
var claims = //fill this variable in your way;
var appIdentity = new ClaimsIdentity(claims);
httpContext.User.AddIdentity(appIdentity);
}
}
await _next(httpContext);
}
}
}
并在您的 Startup.cs
中调用它
app.UseIdentityServer();
app.UseAuthorization();
app.UseMiddleware<ClaimsMiddleware>();
我 运行 IdentityServer4 托管在类似于 https://github.com/IdentityServer/IdentityServer4/tree/master/samples/Quickstarts/6_AspNetIdentity/src/IdentityServerAspNetIdentity 的 MVC 应用程序中。
这个 IdentityServer 主机在 ConfigureServices 方法的底部附近公开了一个这样的配置文件服务。
services.AddTransient<IProfileService, ProfileService>();
从我看过的所有示例和快速入门中,我没有看到 IdentityServer MVC 主机本身可以在哪里访问配置文件数据。这意味着 IDServ MVC 主机是其自身的客户端,可以访问声明数据。我见过 IDServ 将 OpenIdConnect 添加为外部提供者的示例,但似乎 MVC 应用程序将自己列为外部提供者以便我可以获得 ProfileService 声明数据。
我的 Startup.cs(主机 IDServ MVC 应用程序)看起来像这样
public class Startup
{
public Startup(IConfiguration configuration, IHostingEnvironment env)
{
Configuration = configuration;
HostingEnvironment = env;
}
... removed for brevity
public void ConfigureServices(IServiceCollection services)
{
var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
var connectionString = Configuration.GetConnectionString("connString");
services.AddControllersWithViews();
// configures IIS out-of-proc settings (see https://github.com/aspnet/AspNetCore/issues/14882)
services.Configure<IISOptions>(iis =>
{
iis.AuthenticationDisplayName = "Windows";
iis.AutomaticAuthentication = false;
});
// configures IIS in-proc settings
services.Configure<IISServerOptions>(iis =>
{
iis.AuthenticationDisplayName = "Windows";
iis.AutomaticAuthentication = false;
});
services.AddDbContext<AuthDbContext>(b =>
b.UseSqlServer(connectionString,
sqlOptions =>
{
sqlOptions.MigrationsAssembly(typeof(AuthDbContext).GetTypeInfo().Assembly.GetName().Name);
sqlOptions.EnableRetryOnFailure(5, TimeSpan.FromSeconds(1), null);
})
);
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<AuthDbContext>()
.AddDefaultTokenProviders();
services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
})
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = b => b.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly));
})
.AddOperationalStore(options =>
{
options.ConfigureDbContext = b => b.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly));
options.EnableTokenCleanup = true;
})
.AddAspNetIdentity<ApplicationUser>()
.AddSigningAuthority(HostingEnvironment, Configuration)
.AddProfileService<ProfileService>();
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies");
services.AddTransient<IProfileService, ProfileService>();
services.AddTransient<AzureTableStorageLoggerMiddleware>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory, IHttpContextAccessor accessor)
{
app.UseMiddleware<AzureTableStorageLoggerMiddleware>();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthorization();
loggerFactory.AddTableStorage(env.EnvironmentName + "Auth", Configuration["AzureStorageConnectionString"], accessor);
app.UseMiddleware<AzureTableStorageLoggerMiddleware>();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
}
正如您在上面看到的,我没有在其自身上使用 .AddOpenIdConnect,我想知道是否需要在主机本身上添加它,以便我可以像这样在主机 IDServ 应用程序上获取配置文件服务声明数据。 ..
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.Authority = "https://localhost:44378/"; //seems silly to have it point to it's own host
options.RequireHttpsMetadata = false;
options.GetClaimsFromUserInfoEndpoint = true;
options.ClientId = "idserv";
options.ClientSecret = "<<>>";
options.ResponseType = "code id_token token";
options.SaveTokens = true;
});
好的一面是,使用 .AddOpenIdConnect() 中间件方法时,完全独立的 MVC 客户端确实会获取 ProfileService 声明数据,而不是主机。
谢谢
- 正如 IdentityServer 的文档所说:
You can provide a callback to transform the claims of the incoming token after validation. Either use the helper method, e.g.:
services.AddLocalApiAuthentication(principal =>
{
principal.Identities.First().AddClaim(new Claim("additional_claim", "additional_value"));
return Task.FromResult(principal);
});
中阅读完整指南
- 您可以编写新的中间件并加载用户声明。
public class ClaimsMiddleware
{
private readonly RequestDelegate _next;
public ClaimsMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext httpContext, UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager)
{
if (httpContext.User != null && httpContext.User.Identity.IsAuthenticated)
{
var sub = httpContext.User.Claims.SingleOrDefault(c => c.Type == JwtClaimTypes.Subject);
if (sub != null)
{
var user = await userManager.FindByIdAsync(sub.Value);
if (user != null)
{
var claims = //fill this variable in your way;
var appIdentity = new ClaimsIdentity(claims);
httpContext.User.AddIdentity(appIdentity);
}
}
await _next(httpContext);
}
}
}
并在您的 Startup.cs
中调用它 app.UseIdentityServer();
app.UseAuthorization();
app.UseMiddleware<ClaimsMiddleware>();