ASP.NET 4.6.2 Web API 的 Identity Server 4 导致重定向

Identity Server 4 with ASP.NET 4.6.2 Web API causes redirect

我正在尝试将 Identity Server 4 与使用 .NET Framework 4.6.2 编写的 Web API 结合使用。我正在使用 IdentityServer3.Contrib.AccessTokenValidation 答案中提到的 IdentityServer3.Contrib.AccessTokenValidation 库。但是,对受保护端点的调用返回 402 重定向。

这是来自 API 的代码:

private void ConfigureAuth(IAppBuilder app)
        {
            app.Map("/api",
                    apiApp =>
                    {
                        apiApp.UseCors(corsOptions);
                        apiApp.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
                        {
                            Authority = apiAuthority,
                            ValidationMode = ValidationMode.ValidationEndpoint,
                            RequiredScopes = new[] { "api" }
                        });
                        SetupOpenIdAuthentication(apiApp);
                    });
        }

        private static void SetupOpenIdAuthentication(IAppBuilder app)
        {
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = "Cookies",
                ExpireTimeSpan = new TimeSpan(0, Configuration.SessionTimeoutInMinutes, 0),
                SlidingExpiration = true
            });
            app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
            {
                Authority = apiAuthority,
                ClientId = "clientId",
                RedirectUri = apiRootUri + "Help",
                PostLogoutRedirectUri = apiRootUri,
                ResponseType = "id_token token",
                Scope = "openid profile roles api all_claims",
                SignInAsAuthenticationType = "Cookies",
                Notifications = new OpenIdConnectAuthenticationNotifications()
                {
                    RedirectToIdentityProvider = n =>
                    {
                        // if signing out, add the id_token_hint
                        if (n.ProtocolMessage.RequestType == Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectRequestType.Logout)
                        {
                            var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token");
                            if (idTokenHint != null)
                            {
                                n.ProtocolMessage.IdTokenHint = idTokenHint.Value;
                            }
                        }

                        if (n.ProtocolMessage.RequestType == Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectRequestType.Authentication)
                        {
                            if (IsAjaxRequest(n.Request) && n.Response.StatusCode == (int)System.Net.HttpStatusCode.Unauthorized)
                            {
                                n.Response.StatusCode = (int)System.Net.HttpStatusCode.Unauthorized;
                                n.HandleResponse();
                                return Task.FromResult(0);
                            }
                        }

                        return Task.FromResult(0);
                    },
                    SecurityTokenValidated = n =>
                    {
                        // keep the id_token for logout
                        n.AuthenticationTicket.Identity.AddClaim(
                            new Claim("id_token", n.ProtocolMessage.IdToken));

                        //Add Role claims as MS claims so Authorize works on API methods when used without the bearer token
                        foreach (var claim in n.AuthenticationTicket.Identity.Claims.Where(x => x.Type == JwtClaimTypes.Role).ToList())
                        {
                            n.AuthenticationTicket.Identity.AddClaim(new Claim(ClaimTypes.Role, claim.Value));
                        }

                        return Task.FromResult(0);
                    },
                    AuthenticationFailed = n =>
                    {
                        // Pass in the context back to the app
                        n.OwinContext.Response.Redirect("/Help/Error");
                        //TODO:  Create authentication failure page
                        n.HandleResponse(); // Suppress the exception
                        return Task.FromResult(0);
                    }
                }
            });
        }

来自身份服务器的代码:

var builder = services.AddIdentityServer(options =>
            {
                options.Events.RaiseErrorEvents = true;
                options.Events.RaiseInformationEvents = true;
                options.Events.RaiseFailureEvents = true;
                options.Events.RaiseSuccessEvents = true;

                // see https://identityserver4.readthedocs.io/en/latest/topics/resources.html
                options.EmitStaticAudienceClaim = true;
            })
            .AddSigningCredential(rsaCertificate)
            .AddInMemoryIdentityResources(IdentityConfig.IdentityResources)
            .AddInMemoryApiScopes(IdentityConfig.ApiScopes)
            .AddInMemoryClients(IdentityConfig.Clients)
            .AddAspNetIdentity<User>()
            .AddProfileService<CustomProfileService>()
            .AddWsFederationPlugin(options =>
            {
                options.Licensee = "License";
                options.LicenseKey = "Key"
            })
            .AddInMemoryRelyingParties(new List<RelyingParty>());

            services.AddAuthentication(sharedOptions =>
            {
                sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                sharedOptions.DefaultChallengeScheme = WsFederationDefaults.AuthenticationScheme;
            })
            .AddWsFederation(options =>
            {
                options.Wtrealm = "azureAppId";
                options.MetadataAddress = "metadataAddress";
            });
            builder.Services.ConfigureApplicationCookie(options =>
                {
                    options.Cookie.IsEssential = true;
                    options.Cookie.SameSite = SameSiteMode.None; //SameSiteMode.Unspecified in .NET Core 3.1
                });

以下是在Identity Server中为API注册的客户端:

new Client
            {
                ClientId = "clientId",
                AllowedGrantTypes = GrantTypes.ImplicitAndClientCredentials,
                ClientSecrets = { new Secret("secret".Sha256()) },
                RedirectUris = { "https://localhost:44302/", $"{apiUrl}/Help" },
                PostLogoutRedirectUris = { $"{apiUrl}" },
                AllowedScopes = new List<string>
                {
                    IdentityServerConstants.StandardScopes.OpenId,
                    IdentityServerConstants.StandardScopes.Profile,
                    "api",
                    "roles",
                    "all_claims"
                },
                RequirePkce = false,
                AllowOfflineAccess = true,
                AllowAccessTokensViaBrowser = true
            }

如果我需要添加更多代码,请告诉我。当用于调用受 Identity Server 4 保护的 .NET Core API 时,相同的访问令牌不会导致重定向。但是,当在 .NET 4.6.2 中与 API 一起使用时,它会导致重定向。

在 API 中,将 ValidationMode = ValidationMode.ValidationEndpoint 更改为 ValidationMode = ValidationMode.Both。这将使 Identity Server 能够使用 JWT 的本地验证和参考令牌的验证端点。