【发布时间】: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 服务器,实现了GraphQLAPI,并在 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