【问题标题】:.NET Core Angular client SPA has issue redirecting on logout to IdentityServer4 IDP.NET Core Angular 客户端 SPA 在注销时重定向到 IdentityServer4 IDP 时出现问题
【发布时间】:2021-04-29 13:49:20
【问题描述】:

我有一个需要进行身份验证的 Angular 客户端。此客户端托管在 .NetCore SPA 中。现在使用 IdentityServer4,我已经设置了 IDP 来验证客户端。

一切正常登录。此处客户端自动重定向到 IDP 项目进行登录。输入凭据后,它将重定向回客户端应用程序。

但是,对于注销它不起作用。在 .netcore 中作为 SPA 托管的 Angular 客户端上,我有一个注销按钮。此注销按钮事件将调用同一客户端中的 API 控制器。在这个控制器中,有从 httpContext 注销的代码。

我的期望是,当在控制器中调用注销操作时,它将注销并将我重定向回 IDP。积极的一面是,我在 MVC 客户端(没有 SPA 和 Angular)中实现了相同的功能,它通过将我重定向回 IDP 来实现注销。

另外一个积极的方面是,在 Angular 客户端上注销时它实际上会尝试注销,但 CORS 策略正在阻止它。浏览器控制台中的错误消息将重定向 url 作为错误消息的一部分。当我通过将我重定向到 IDP 并注销我单击它时,此 url 有效。

我无法弄清楚我必须添加什么 CORS 策略才能使此重定向工作。我尝试将 CORS 添加到启动类,但没有奏效。请帮忙

浏览器错误信息:

从源“https://localhost:44374”访问“https://localhost:44336/connect/endsession?post_logout_redirect_uri=https%3A%2F%2Flocalhost%3A44374%2Fsignout-callback-oidc&id_token_hint=eyJhbGciOiJSUzI1NiIsImtpZCI6IlMwbFpqUi1QazItS0dLc2xxaFlQQ2ciLCJ0eXAiOiJKV1QifQ.eyJuYmYiOjE1NzkzNDc3NDcsImV4cCI6MTU3OTM0ODA0NywiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NDQzMzYiLCJhdWQiOiJBY3Rpdml0eVRyYWNrZXJOZ0NsaWVudF9DbGllbnRJZCIsIm5vbmNlIjoiNjM3MTQ5NDQ1NDMyMDYwODM5LllUVmpaV1UyTURjdE5ERmhOeTAwTW1SbExXRm1NVGd0TWpKaVl6VTFOMlJoWkRkaE9EYzNPV1EwTm1VdFpEUmxaUzAwT1RNMkxUZ3hOV0l0T0RJeU5HWTVaR1l4T0RsaSIsImlhdCI6MTU3OTM0Nzc0NywiYXRfaGFzaCI6ImJwc3hLLThSU2Nwb2hKMnJPSlViQlEiLCJzX2hhc2giOiJ0M2R1RmljZDR1VTRTQlQ3S253cG1nIiwic2lkIjoiYTFIWnE5YXFMdnR1WHB6S1FUZUVvUSIsInN1YiI6ImQ4NjBlZmNhLTIyZDktNDdmZC04MjQ5LTc5MWJhNjFiMDdjNyIsImF1dGhfdGltZSI6MTU3OTM0Nzc0NywiaWRwIjoibG9jYWwiLCJhbXIiOlsicHdkIl19.bd35dk-lcolUxgoNAzzc4kKIORQIsmeSu5JaARpyqj1I6cv5P6LSHrcdw3YmZ80q_tF8WLi7ywIml-enEP4JAe-nbYw7gSlFt9qHtw5eSF37dMdBZq7UUXt6EoK29xs9lp6TyIB11pzgRZ8tPVAPw0Y8rNpGSGYtjfWjp7t4FdKthvUchAo_SNh6l40S5oV0Yo_YIWfHtjxM-nLZXia0YCvjNEQChmTmkzSMCIdGnVqawhIzQ_O7jv0c1T7kCwaF5YGyer3ZUyj1UM53JTBbbGpKDrDh2DV-kd4tvhoaLnWQAoUqCQ1Ofl_kHc8vffqE7RRPGmQLQYOM48186hIe0g&state=CfDJ8DRPXADjz9hKioMAFvg6DCP1P37ODZ4R81EV3uFXBpxiOLWoJY6GDEcbYNZzB--zZjv-Z94PSfMJkcoJhQcmHAvmM_9yKL9hPaGqmucpJrO_wv74Fj8bmdm8C7l_MJZ3VaNahF5Bqvi9tWFUikbr-HJ_uI0GiGX6qsj5mkrp8K4x&x-client-SKU=ID_NETSTANDARD2_0&x-client-ver=5.5.0.0”处的 XMLHttpRequest(从“https://localhost:44374/api/Authorization/Logout”重定向)已被 CORS 策略阻止:没有“Access-Control-Allow-Origin”标头出现在请求的资源

以及 API 控制器注销代码:

    [HttpGet]
    public async Task Logout()
    {
        var httpclient = new HttpClient();
        var disco = await httpclient.GetDiscoveryDocumentAsync("https://localhost:44336/");

        // get the access token to revoke 
        var accessToken = await HttpContext
          .GetTokenAsync(OpenIdConnectParameterNames.AccessToken);

        if (!string.IsNullOrWhiteSpace(accessToken))
        {
            var revokeAccessTokenResponse =
                await httpclient.RevokeTokenAsync(new TokenRevocationRequest
                {
                    Address = disco.RevocationEndpoint,
                    ClientId = "App_ClientId",
                    ClientSecret = "someSecret",

                    Token = accessToken
                });

            if (revokeAccessTokenResponse.IsError)
            {
                throw new Exception("Problem encountered while revoking the access token."
                    , revokeAccessTokenResponse.Exception);
            }
        }

        // Clears the  local cookie ("Cookies" must match name from scheme)
        await HttpContext.SignOutAsync("Cookies");
        await HttpContext.SignOutAsync("oidc");
    }

具有完整客户端配置的 Startup.cs

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllersWithViews();
        // In production, the Angular files will be served from this directory
        services.AddSpaStaticFiles(configuration =>
        {
            configuration.RootPath = "ClientApp/dist";
        });

        services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        services.AddScoped<ActivityTrackerAPIHttpClient>();

        services.AddAuthentication(options =>
        {
            options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = "oidc";
        }).AddCookie("Cookies")
        .AddOpenIdConnect("oidc", options =>
        {
            options.SignInScheme = "Cookies";
            options.Authority = Configuration["IdentityServer:Authority"];
            options.ClientId = Configuration["IdentityServer:ClientId"];
            options.ClientSecret = Configuration["IdentityServer:ClientSecret"];
            options.ResponseType = "code id_token";
            options.Scope.Add("openid");
            options.Scope.Add("profile");
            options.Scope.Add("roles");
            options.Scope.Add(Configuration["IdentityServer:ApiName"]);
            options.Scope.Add("offline_access");
            options.SaveTokens = true;
            options.GetClaimsFromUserInfoEndpoint = true;
            options.ClaimActions.Remove("amr");
            options.ClaimActions.DeleteClaim("sid");
            options.ClaimActions.DeleteClaim("idp");
            options.ClaimActions.MapJsonKey("role", "role");

            options.TokenValidationParameters = new TokenValidationParameters
            {
                RoleClaimType = JwtClaimTypes.Role,
            };
        });

        services.ConfigureLoggerService();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/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.UseAuthentication();

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        if (!env.IsDevelopment())
        {
            app.UseSpaStaticFiles();
        }

        app.UseRouting();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller}/{action=Index}/{id?}");
        });

        app.Use(async (context, next) =>
        {
            if (!context.User.Identity.IsAuthenticated)
            {
                await context.ChallengeAsync("oidc");
            }
            else
            {
                await next();
            }
        });

        app.UseSpa(spa =>
        {
            // To learn more about options for serving an Angular SPA from ASP.NET Core,
            // see https://go.microsoft.com/fwlink/?linkid=864501

            spa.Options.SourcePath = "ClientApp";

            if (env.IsDevelopment())
            {
                spa.UseAngularCliServer(npmScript: "start");
            }
        });
    }

【问题讨论】:

  • 为什么你去你的 api 来撤销令牌,而不是直接从你的客户那里去 idp?如果您使用的是 oidc.js,您可以从您的客户端使用 this.mgr.signoutRedirect()
  • 我使用的是 Hybrid Flow 而不是 Implicit Flow。所以我的角度客户对授权一无所知。授权是在托管 Angular 的 .Net 核心 MVC 角度 SPA 上设置的。当我登录时重定向到 IPD 工作。但是对于注销,重定向到 IDP 被 CORS 阻止。我已经更新了上面的代码,以反映登录重定向是如何实现的,这与注销略有不同。
  • 请发Client配置
  • Pablo,我已经用整个客户端配置更新了问题。
  • 对不起@JoeIpe 我说的是 IdentityServer 上的客户端配置

标签: .net-core cors identityserver4 openid-connect


【解决方案1】:

当时我想出了一个临时解决方案。但是我意识到,最好在 Angular 方面进行客户端安全。

下面是我的临时解决方案。

客户端的Startup.cs

options.Events = new Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectEvents
                    {
                        OnRedirectToIdentityProviderForSignOut = (context) =>
                        {
                            var protocolMessage = context.ProtocolMessage;
                            var param = "";
                            foreach (var parameter in protocolMessage.Parameters)
                            {
                                param += $"{parameter.Key}={parameter.Value}$";
                            }
                            var url = $"{protocolMessage.IssuerAddress}?{param}x-client-SKU={protocolMessage.SkuTelemetryValue}&x-client-ver=5.5.0.0";

                            context.HttpContext.Session.SetString("LogoutUrl", url);

                            return Task.FromResult(0);
                        }
                    };

还可以通过在 ConfigureServices 方法中添加 services.AddSession(); 然后在 Configure 方法中添加 app.UseSession(); 在客户端 startup.cs 中启用会话。

在控制器中

[HttpGet]
    public async Task<IActionResult> Logout()
    {
        await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
        await HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme);

        var url = HttpContext.Session.GetString("LogoutUrl");

        return Ok(url);
    }

【讨论】:

    猜你喜欢
    • 2017-09-01
    • 1970-01-01
    • 2021-02-19
    • 2019-11-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-12
    • 1970-01-01
    相关资源
    最近更新 更多