如何在IdentityServer4中结合Identity.Application与JWT认证
ASP.NET 6 授权/资源服务器身份识别问题
我正在实现一个兼具授权服务器与资源服务器功能的ASP.NET 6应用,已为独立部署的Angular SPA配置了带PKCE的授权码流,并添加了Identity Razor页面。
尝试了多个代码示例,仅以下代码有效,但必须为AuthorizeAttribute设置AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme:
services.AddAuthentication() .AddJwtBearer(options => { // 授权服务器基础URL // 服务配置阶段无法获取基础URL,因此硬编码 options.Authority = _webHostEnvironment.IsDevelopment() ? "https://localhost:5000" : "https://threadbox.prod"; // API资源 options.Audience = "threadbox_api"; });
此配置下,仅能在授权处理器(PermissionHandler.cs)中读取身份,其认证类型为AuthenticationTypes.Federation;而中间件(PermissionsMiddleware.cs)仅能识别认证类型为Identity.Application的身份。
Startup代码
services .AddDefaultIdentity<ApplicationUser>(options => { options.SignIn.RequireConfirmedAccount = false; }) .AddRoles<IdentityRole>() .AddEntityFrameworkStores<ApplicationDbContext>(); var identityServerBuilder = services.AddIdentityServer(); if (_webHostEnvironment.IsDevelopment()) { identityServerBuilder.AddDeveloperSigningCredential(); } else { // 使用SSL证书 } identityServerBuilder .AddAspNetIdentity<ApplicationUser>() .AddOperationalStore(options => { options.ConfigureDbContext = builder => { builder.UseNpgsql( _configuration.GetConnectionString(AppSettings.ConnectionStrings.Development), // TODO: 程序集名称不应硬编码 options => options.MigrationsAssembly("ThreadboxApi")); }; options.EnableTokenCleanup = true; }) .AddInMemoryIdentityResources(new List<IdentityResource> { new IdentityResources.OpenId(), new IdentityResources.Profile() }) .AddInMemoryApiScopes(new List<ApiScope> { new ApiScope("threadbox_api.access", "Threadbox API"), }) .AddInMemoryApiResources(new List<ApiResource> { new ApiResource { Name = "threadbox_api", Scopes = { "threadbox_api.access" } } }) .AddInMemoryClients(new List<Client> { new Client { ClientId = "angular_client", ClientName = "Threadbox", AllowedCorsOrigins = { _configuration[AppSettings.ClientBaseUrl] }, AllowedGrantTypes = GrantTypes.Code, RequirePkce = true, RequireClientSecret = false, AllowOfflineAccess = true, AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, IdentityServerConstants.StandardScopes.OfflineAccess, "threadbox_api.access" }, RedirectUris = { _configuration[AppSettings.ClientBaseUrl] + "/authorization/sign-in-redirect-callback", _configuration[AppSettings.ClientBaseUrl] + "/authorization/sign-in-silent-callback" }, PostLogoutRedirectUris = { _configuration[AppSettings.ClientBaseUrl] + "/authorization/sign-out-redirect-callback" }, }, }); services.AddAuthentication() .AddJwtBearer(options => { // 授权服务器基础URL // 服务配置阶段无法获取基础URL,因此硬编码 options.Authority = _webHostEnvironment.IsDevelopment() ? "https://localhost:5000" : "https://threadbox.prod"; // API资源 options.Audience = "threadbox_api"; }); services.AddAuthorization(); services.AddSingleton<IAuthorizationHandler, PermissionHandler>(); services.AddSingleton<IAuthorizationPolicyProvider, PermissionPolicyProvider>(); services.AddAutoMapper(Assembly.GetExecutingAssembly()); services.AddMediatR(configuration => { configuration.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()); }); services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly()); services.AddFluentValidationAutoValidation(); services.AddMvc(); services.ConfigureApplicationCookie(options => { // 禁用未授权HTTP请求的重定向 options.Events.OnRedirectToLogin = context => { context.Response.StatusCode = StatusCodes.Status401Unauthorized; return Task.CompletedTask; }; // 禁用禁止访问HTTP请求的重定向 options.Events.OnRedirectToAccessDenied = context => { context.Response.StatusCode = StatusCodes.Status403Forbidden; return Task.CompletedTask; }; }); app.UseExceptionHandler(); // 启用HTTP路由 app.UseRouting(); app.UseAuthentication(); app.UseMiddleware<PermissionsMiddleware>(); app.UseIdentityServer(); app.UseAuthorization();
PermissionsMiddleware代码
public class PermissionsMiddleware { private readonly RequestDelegate _next; public PermissionsMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context, IdentityService identityService) { if (context.Request.Path == "/api/Boards/GetBoardsList") { var x = context.User.Identity.AuthenticationType; } if (context.User.Identity.AuthenticationType == "Identity.Application" || context.User.Identity.AuthenticationType == null) { await _next(context); return; } var userId = context.User.FindFirst("sub")?.Value; //if (userId == null) //{ // await _next(context); // return; //} var permissionsIdentity = await identityService.GetUserPermissionsIdentity(userId); if (permissionsIdentity != null) { context.User.AddIdentity(permissionsIdentity); } await _next(context); } }
PermissionHandler代码
public class PermissionHandler : AuthorizationHandler<PermissionRequirement> { public PermissionHandler() { } protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement) { var hasPermission = context.User.HasClaim(PermissionContants.ClaimType, requirement.Permission); if (!hasPermission) { context.Fail(); return Task.CompletedTask; } context.Succeed(requirement); return Task.CompletedTask; } }
内容的提问来源于stack exchange,提问作者Vlad Barkovsky
相关产品推荐
相关产品推荐

