【问题标题】:IdentityServer4 throws HTTP 404 when redirecting to /sigin-oidcIdentityServer4 在重定向到 /sigin-oidc 时抛出 HTTP 404
【发布时间】:2021-01-23 20:36:31
【问题描述】:

我正在尝试设置 3 个 Web 应用程序,如下所示:

  • Gateway 是身份服务器,使用 IdentityServer4 v4.1.0 并在 localhost:7443 上运行
  • Web Frontend 是 Web UI 服务器,渲染一个 React 应用程序,并在 localhost:8443 上运行
  • Backend 是 Web API 服务器,实现了 GraphQL API,并在 localhost:9443 上运行

以上所有内容都托管在ASP.NET Core 3.1 应用程序中。

我确定我犯了一些新手错误,但是当通过WebFrontend 登录时,我在Gateway 上获得了登录页面,我可以登录,但在重定向时,我得到了 /signin 的 HTTP 404 -oidc 在Gateway 上。

这是我得到的:

但是,我可以导航到Gateway 上的登录页面并登录。

我做错了什么?

配置如下:

Config.cs

using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Text.Json;
using IdentityModel;
using IdentityServer4;
using IdentityServer4.Models;
using IdentityServer4.Test;

namespace Bakhtawar.Data
{
    public static class Config
    {
        public static IEnumerable<IdentityResource> IdentityResources =>
            new IdentityResource[]
            {
                new IdentityResources.OpenId
                {
                    DisplayName = "User identifier",
                    Description = "Your user identifier"
                },
                new IdentityResources.Profile
                {
                    DisplayName = "User profile",
                    Description = "Your user profile information (first name, last name, etc.)"
                },
                new IdentityResources.Email
                {
                    DisplayName = "User identifier",
                    Description = "Your user identifier"
                }
            };

        public static IEnumerable<ApiScope> ApiScopes =>
            new ApiScope[]
            {
                new ApiScope("bakhtawar.users"),
                new ApiScope("bakhtawar.galleries"),
                new ApiScope("bakhtawar.posts"),
                new ApiScope("bakhtawar.comments")
            };

        public static IEnumerable<Client> Clients =>
            new Client[]
            {
                new Client
                {
                    ClientId = "bakhtawar.api",
                    ClientName = "Bakhtawar API",
                    ClientSecrets =
                    {
                        new Secret("893bfc0b-880c-4f5e-b258-41d007e08860".Sha256())
                    },
                    AllowedGrantTypes = GrantTypes.ClientCredentials,
                    AllowedScopes =
                    {
                        "bakhtawar.users",
                        "bakhtawar.galleries",
                        "bakhtawar.posts",
                        "bakhtawar.comments"
                    }
                },
                new Client
                {
                    ClientId = "bakhtawar.web",
                    ClientName = "Bakhtawar Web",
                    ClientSecrets =
                    {
                        new Secret("ca39181f-12ce-4a44-a4fd-0955b39c4953".Sha256())
                    },
                    AllowedGrantTypes = GrantTypes.Code,
                    RedirectUris =
                    {
                        "https://localhost:7443/signin-oidc"
                    },
                    FrontChannelLogoutUri = "https://localhost:7443/signout-oidc",
                    PostLogoutRedirectUris =
                    {
                        "https://localhost:7443/signout-callback-oidc"
                    },
                    AllowedScopes =
                    {
                        "openid",
                        "profile",
                        "email",
                        "bakhtawar.users",
                        "bakhtawar.galleries",
                        "bakhtawar.posts",
                        "bakhtawar.comments"
                    },
                    AllowOfflineAccess = true,
                    RequireClientSecret = false
                },
                new Client
                {
                    ClientId = "bakhtawar.app",
                    ClientName = "Bakhtawar App",
                    ClientSecrets =
                    {
                        new Secret("bd785003-0cce-4a77-9fec-516f033e3043".Sha256())
                    },
                    RedirectUris = { "urn:ietf:wg:oauth:2.0:oob" },
                    PostLogoutRedirectUris = { "https://notused" },
                    RequireClientSecret = false,
                    AllowedGrantTypes = GrantTypes.Code,
                    AllowedScopes =
                    {
                        "openid",
                        "profile",
                        "email",
                        "bakhtawar.users",
                        "bakhtawar.galleries",
                        "bakhtawar.posts",
                        "bakhtawar.comments"
                    },
                    AllowOfflineAccess = true,
                    RefreshTokenUsage = TokenUsage.OneTimeOnly,
                    RefreshTokenExpiration = TokenExpiration.Sliding
                }
            };
    }
}

每个人的Startup.cs文件如下:

网关/Startup.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Bakhtawar.Apps.GatewayApp.Contracts;
using Bakhtawar.Apps.GatewayApp.Services;
using Bakhtawar.Data;
using Bakhtawar.Models;
using IdentityServer4;
using IdentityServer4.Services;
using IdentityServer4.Validation;
using Microsoft.AspNetCore.ApiAuthorization.IdentityServer;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Slugify;

namespace Bakhtawar.Apps.GatewayApp
{
    public class Startup
    {
        public Startup(IConfiguration configuration, IWebHostEnvironment environment)
        {
            Configuration = configuration;
            Environment = environment;
        }

        public IConfiguration Configuration { get; }

        public IWebHostEnvironment Environment { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services
                .AddDbContext<IdentityDbContext>
                (
                    (builder) => builder.UseSqlServer(Configuration["ConnectionStrings:Identity"], b => b.MigrationsAssembly("Bakhtawar.Apps.GatewayApp"))
                );
            
            services
                .AddIdentity<User, Role>
                (
                    (options) =>
                    {
                        options.SignIn.RequireConfirmedAccount = true;
                        options.Password.RequireNonAlphanumeric = false;
                    }
                )
                .AddDefaultTokenProviders();

            services
                .AddSingleton<EFUserStore>()
                .AddSingleton<EFRoleStore>();

            services
                .AddSingleton<IUserStore<User>>((serviceProvider) => serviceProvider.GetService<EFUserStore>())
                .AddSingleton<IUserEmailStore<User>>((serviceProvider) => serviceProvider.GetService<EFUserStore>())
                .AddSingleton<IUserPhoneNumberStore<User>>((serviceProvider) => serviceProvider.GetService<EFUserStore>())
                .AddSingleton<IUserPasswordStore<User>>((serviceProvider) => serviceProvider.GetService<EFUserStore>())
                .AddSingleton<IUserLoginStore<User>>((serviceProvider) => serviceProvider.GetService<EFUserStore>())
                .AddSingleton<IUserLockoutStore<User>>((serviceProvider) => serviceProvider.GetService<EFUserStore>());

            services
                .AddSingleton<IRoleStore<Role>>((serviceProvider) => serviceProvider.GetService<EFRoleStore>());

            services
                .AddIdentityServer
                (
                    (options) =>
                    {
                        options.Events.RaiseErrorEvents = true;
                        options.Events.RaiseFailureEvents = true;
                        options.Events.RaiseInformationEvents = true;
                        options.Events.RaiseSuccessEvents = true;
                        
                        // HINT : see https://identityserver4.readthedocs.io/en/latest/topics/resources.html
                        options.EmitStaticAudienceClaim = true;
                    }
                )
                .AddAspNetIdentity<User>()
                // NOTE : adds the config data from DB (clients, resources, CORS)
                .AddConfigurationStore
                (
                    (options) =>
                    {
                        options.ConfigureDbContext = (builder) => builder.UseSqlServer(Configuration["ConnectionStrings:Identity"], b => b.MigrationsAssembly("Bakhtawar.Apps.GatewayApp"));
                    }
                )
                // NOTE : adds the operational data from DB (codes, tokens, consents)
                .AddOperationalStore
                (
                    (options) =>
                    {
                        options.ConfigureDbContext = (builder) => builder.UseSqlServer(Configuration["ConnectionStrings:Identity"], b => b.MigrationsAssembly("Bakhtawar.Apps.GatewayApp"));

                        // NOTE: enables automatic token cleanup. this is optional.
                        options.EnableTokenCleanup = true;
                    }
                )
                .AddServices
                (
                    Environment.IsDevelopment(),
                    (builder) => builder.AddDeveloperSigningCredential(false),
                    (builder) => builder.AddSigningCredential
                    (
                        new X509Certificate2
                        (
                            File.ReadAllBytes(Configuration["IdentityServer:Key:FilePath"]),
                            (string) Configuration["IdentityServer:Key:Password"]
                        )
                    )
                );

            services
                .AddAuthentication()
                .AddCookie("Cookies")
                .AddService
                (
                    Configuration["Secret:Google:ClientId"] != null && Configuration["Secret:Google:ClientSecret"] != null,
                    (builder) => builder
                        .AddGoogle
                        (
                            "Google",
                            (options) =>
                            {
                                options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;

                                // NOTE : register your IdentityServer with Google at https://console.developers.google.com
                                //        enable the Google+ API
                                //        set the redirect URI to https://localhost:4443/signin-google & https://id.bakhtawar.co.uk/signin-google
                                options.ClientId = Configuration["Secret:Google:ClientId"];
                                options.ClientSecret = Configuration["Secret:Google:ClientSecret"];
                            }
                        )
                )
                .AddLocalApi
                (
                    (options) =>
                    {
                        options.ExpectedScope = "api";
                    }
                );

            services
                .AddOidcStateDataFormatterCache("aad");

            services
                .AddControllersWithViews();

            services
                .AddRazorPages();
            
            services
                .AddSameSiteCookiePolicy();

            services
                .AddCors
                (
                    (options) =>
                    {
                        options.AddPolicy
                        (
                            "api",
                            (policy) =>
                            {
                                policy
                                    .AllowAnyOrigin()
                                    .AllowAnyHeader()
                                    .AllowAnyMethod();
                            }
                        );
                    }
                );

            services
                .AddScoped<IUserProvisioner<User>, UserProvisioner>();

            services
                .AddScoped<IClientRequestParametersProvider, ClientRequestParametersProvider>();

            services
                .AddScoped<IAbsoluteUrlGenerator, AbsoluteUrlGenerator>();

            services
                .AddTransient<IPasswordValidator, PasswordValidator>();

            services
                .AddTransient<IRedirectUriValidator, DoNothingRedirectValidator>();

            services
                .AddTransient<ICorsPolicyService, DoNothingCorsPolicyService>();

            services
                .AddSingleton<IEmailSender, FileSystemEmailSender>();

            services
                .AddSingleton<SlugHelper>();

            services.Configure<ForwardedHeadersOptions>
            (
                (options) =>
                {
                    options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedHost | ForwardedHeaders.XForwardedProto;

                    if (!Environment.IsDevelopment())
                    {
                        var knownNetworks = Configuration["ForwardedHeadersOptions:KnownNetworks"];

                        if (!string.IsNullOrEmpty(knownNetworks))
                        {
                            foreach (var knownNetwork in knownNetworks.Split(";"))
                            {
                                var parts = knownNetwork.Split(":");

                                var prefix = parts[0];
                                var prefixLength = int.Parse(parts[1]);

                                options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse(prefix), prefixLength));
                            }
                        }
                    }
                }
            );
        }

        public void Configure(IApplicationBuilder app)
        {
            app.UseForwardedHeaders();

            if (Environment.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseDatabaseErrorPage();
            }
            else
            {
                app.UseHsts();
            }

            app.UseHttpsRedirection();

            app.UseCookiePolicy();

            app.UseCors("api");

            app.UseStaticFiles();

            app.UseRouting();

            app.UseAuthentication();

            app.UseIdentityServer();

            app.UseAuthorization();

            app.UseEndpoints
            (
                (endpoints) =>
                {
                    endpoints.MapDefaultControllerRoute();
                    endpoints.MapRazorPages();
                }
            );
        }
    }

    public static class SameSiteCookiePolicyExtensions
    {
        public static IServiceCollection AddSameSiteCookiePolicy(this IServiceCollection services)
        {
            services.Configure<CookiePolicyOptions>
            (
                (options) =>
                {
                    options.MinimumSameSitePolicy = SameSiteMode.Unspecified;
                    options.OnAppendCookie = (cookieContext) => CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
                    options.OnDeleteCookie = (cookieContext) => CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
                }
            );

            return services;
        }

        private static void CheckSameSite(HttpContext httpContext, CookieOptions options)
        {
            if (options.SameSite == SameSiteMode.None)
            {
                var userAgent = httpContext.Request.Headers["User-Agent"].ToString();

                if (DisallowsSameSiteNone(userAgent))
                {
                    // NOTE : for .NET Core < 3.1, set SameSite = (SameSiteMode)(-1)
                    options.SameSite = SameSiteMode.Unspecified;
                }
            }
        }

        private static bool DisallowsSameSiteNone(string userAgent)
        {
            // NOTE: cover all iOS-based browsers here. This includes:
            // - Safari on iOS 12 for iPhone, iPod Touch, iPad
            // - WkWebview on iOS 12 for iPhone, iPod Touch, iPad
            // - Chrome on iOS 12 for iPhone, iPod Touch, iPad
            // All of which are broken by SameSite=None, because they use the iOS networking stack
            if (userAgent.Contains("CPU iPhone OS 12") || userAgent.Contains("iPad; CPU OS 12"))
            {
                return true;
            }

            // NOTE: cover Mac OS X based browsers that use the Mac OS networking stack. This includes:
            // - Safari on Mac OS X.
            // This does not include:
            // - Chrome on Mac OS X
            // Because they do not use the Mac OS networking stack.
            if (userAgent.Contains("Macintosh; Intel Mac OS X 10_14") &&
                userAgent.Contains("Version/") && userAgent.Contains("Safari"))
            {
                return true;
            }

            // NOTE : cover Chrome 50-69, because some versions are broken by SameSite=None, 
            // and none in this range require it.
            // Note: this covers some pre-Chromium Edge versions, 
            // but pre-Chromium Edge does not require SameSite=None.
            if (userAgent.Contains("Chrome/5") || userAgent.Contains("Chrome/6"))
            {
                return true;
            }

            return false;
        }
    }

    public static class AuthenticationBuilderExtensions
    {
        public static AuthenticationBuilder AddService(this AuthenticationBuilder builder, bool condition, Func<AuthenticationBuilder, AuthenticationBuilder> build)
        {
            if (condition)
            {
                builder = build(builder);
            }
            
            return builder;
        }

        public static AuthenticationBuilder AddService(this AuthenticationBuilder builder, Func<bool> condition, Func<AuthenticationBuilder, AuthenticationBuilder> build)
        {
            return builder.AddService(condition(), build);
        }
    }

    public static class IdentityServerBuilderExtensions
    {
        public static IIdentityServerBuilder AddServices
        (
            this IIdentityServerBuilder identityServerBuilder,
            Func<bool> condition,
            Func<IIdentityServerBuilder, IIdentityServerBuilder> ifTrue,
            Func<IIdentityServerBuilder, IIdentityServerBuilder> ifFalse
        )
        {
            return identityServerBuilder.AddServices(condition(), ifTrue, ifFalse);
        }
        
        public static IIdentityServerBuilder AddServices
        (
            this IIdentityServerBuilder identityServerBuilder,
            bool condition,
            Func<IIdentityServerBuilder, IIdentityServerBuilder> ifTrue,
            Func<IIdentityServerBuilder, IIdentityServerBuilder> ifFalse
        )
        {
            if (condition)
            {
                return ifTrue(identityServerBuilder);
            }
            else
            {
                return ifFalse(identityServerBuilder);
            }
        }
    }
}

WebFrontend/Startup.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Bakhtawar.Data;
using Bakhtawar.Models;
using Bakhtawar.Services;
using IdentityServer4;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;

namespace Bakhtawar.Apps.WebFrontendApp
{
    public class Startup
    {
        public Startup(IConfiguration configuration, IWebHostEnvironment environment)
        {
            Configuration = configuration;
            Environment = environment;
        }

        public IConfiguration Configuration { get; }

        public IWebHostEnvironment Environment { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services
                .AddDbContext<IdentityDbContext>
                (
                    (options) => { options.UseSqlServer(Configuration["ConnectionStrings:Identity"]); }
                );

            services
                .AddAuthentication();

            services
                .AddControllersWithViews
                (
                    (options) =>
                    {
                        options.Conventions.Add(new RouteTokenTransformerConvention(new SlugifyParameterTransformer()));
                    }
                );
            services.AddRazorPages();

            services
                .AddSpaStaticFiles
                (
                    (options) => { options.RootPath = "App/build"; }
                );

            services.Configure<ForwardedHeadersOptions>
            (
                (options) =>
                {
                    options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedHost | ForwardedHeaders.XForwardedProto;

                    if (!Environment.IsDevelopment())
                    {
                        var knownNetworks = Configuration["ForwardedHeadersOptions:KnownNetworks"];

                        if (!string.IsNullOrEmpty(knownNetworks))
                        {
                            foreach (var knownNetwork in knownNetworks.Split(";"))
                            {
                                var parts = knownNetwork.Split(":");

                                var prefix = parts[0];
                                var prefixLength = int.Parse(parts[1]);

                                options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse(prefix), prefixLength));
                            }
                        }
                    }
                }
            );
        }

        public void Configure(IApplicationBuilder app)
        {
            app.UseForwardedHeaders();

            if (Environment.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseDatabaseErrorPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                app.UseHsts();
            }

            app.UseHttpsRedirection();

            app.UseStaticFiles();
            app.UseSpaStaticFiles();

            app.UseRouting();

            app.UseAuthentication();
            app.UseAuthorization();
            app.UseEndpoints
            (
                (endpoints) =>
                {
                    endpoints.MapDefaultControllerRoute();
                    endpoints.MapRazorPages();
                }
            );

            app.UseSpa
            (
                (spa) =>
                {
                    spa.Options.SourcePath = "App";

                    if (Environment.IsDevelopment())
                    {
                        spa.UseReactDevelopmentServer(npmScript: "start");
                    }
                }
            );
        }
    }

    public static class AuthenticationBuilderExtension
    {
        public static AuthenticationBuilder AddService(this AuthenticationBuilder builder, bool condition, Func<AuthenticationBuilder, AuthenticationBuilder> build)
        {
            if (condition)
            {
                builder = build(builder);
            }
            
            return builder;
        }
    }
}

【问题讨论】:

  • 你可以发布mvc应用程序的启动吗?我的意思是https://localhost:7443 上的那个。 identityServer 的 url 是什么?
  • 对不起,这是一个有效的观点。让我用这个细节更新我的问题。
  • 启动是标准的。没什么大不了的,
  • 你在哪里添加了 openID 连接?
  • 我看到你的架构是这样的,如果我错了,请纠正我:1. IdentityServer4 2. React 应用程序(托管在 .net 核心上)3. API - 如果同意,我会发布给你一个答案

标签: asp.net-core authentication identityserver4 openid-connect


【解决方案1】:

/signin-oidc 是 OpenId Connect 身份验证处理程序的远程登录地址。此路由由 OpenId Connect 身份验证中间件处理。这意味着如果您没有 OIDC 中间件,则此路由将不存在。

如果您的 MVC 应用托管 IDS4,则无需为其添加客户端配置。 如果您需要 MVC 应用程序,create a new MVC app

Here我有一个示例,包括 JsClient 和 MVC 客户端。

【讨论】:

  • 将它添加到我的WebFrontend 的正确方法是什么?为什么我会收到 Unable to unprotect message.State 错误?
  • 您分享的链接是旧版本的IdentityServer4。我正在使用v4.1.0
  • 更新了链接。我不确定你是如何将它添加到你的 react 应用程序中的,这里是 js 客户端的文档identityserver4.readthedocs.io/en/latest/quickstarts/… 我还为你发布了我的示例 repo 链接
【解决方案2】:

我认为您需要设置 IssuerUri [此处][1] 以便 IdentityServer 认为它位于网关的公共域中,即客户端看到的域。

https://identityserver4.readthedocs.io/en/latest/reference/options.html

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-08-07
    • 2018-10-30
    • 1970-01-01
    • 2022-11-11
    • 2020-11-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多