我想我会分享我的测试结果,因为它发现了一些我真的没想到的东西。也许这些事情对你们大多数人来说是显而易见的/知道的 - 但对我来说,它们肯定被证明是棘手的。
我学到的第一件重要事情是,您必须针对应用问题的挑战类型进行具体说明。如果您是自托管登录页面,则需要使用:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
如果您使用 Okta(或其他 openId 提供者)登录页面,您需要发出不同类型的质询:
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
我了解到,如果您在 ASP.NET Core 应用程序中使用 OpenID Connect,并且您正在自托管登录页面,则流程如下所示:
- 用户尝试访问受保护的资源
- [Authorize] 标记告诉 Okta 中间件注入自己并执行其操作。 User.Identity.IsAuthenticated 为 false,因此它重定向到 cookie 选项下设置的任何内容作为登录路径:
.AddCookie(options =>
{
options.LoginPath = new PathString("/Account/SignIn");
options.Cookie.Name = ".AspNet.SharedCookie";
})
我的登录页面只是使用 Okta 小部件,因此单击登录会将用户发送到 Okta,Okta 会执行它的操作,然后将用户发送回应用程序到每个神秘的“授权码/回调”url .该 url 由 Okta 中间件处理。
中间件处理从 Okta 发送的令牌,调用 /userinfo 端点以获取声明(假设您将该属性设置为 true)并填充 ClaimsPrincipal。
这是我错过的部分它还可以设置一个 cookie。从那时起,.net 框架将检查该 cookie 以确定用户是谁以及他们是否已通过身份验证。我完全误解了这一点。我以为中间件会在后台调用 /authorize 或类似的东西。
所以 - 我在这里学到的重要教训是:仅仅因为两个应用程序使用相同的 OpenID 服务器并不意味着它们可以轻松共享身份验证/会话。为了让真正的 SSO 无缝工作,他们还必须共享一个 cookie。这让我想到了我的第二个发现:跨 ASP.NET 核心应用程序共享 cookie。简短的版本是您必须:
一个。在两个应用程序中将 cookie 命名为相同
湾。使用 IDataProtection 接口将 cookie 密钥存储在两个应用程序都可以访问的位置(我选择了 Azure)。
C。确保两个应用的 cookie 域相同
所以在一天结束时,这就是我的代码端到端的样子(在每个应用程序的启动中)。我仍在处理一些特定于 Nopcommerce 的奇怪问题,但希望它们能很快在这里得到解决。
var oktaMvcOptions = new OktaMvcOptions()
{
OktaDomain = oktaDomain,
ClientId = clientId,
ClientSecret = clientSecret,
Scope = new List<string> { "openid", "profile", "email", "address", "groups" },
AuthorizationServerId = authServerId,
GetClaimsFromUserInfoEndpoint = true
};
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(storageConnectionString);
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
CloudBlobContainer container = blobClient.GetContainerReference(storageContainerName);
var blob = container.GetBlockBlobReference("somefilename.xml");
services.AddDataProtection().SetApplicationName("somesharedappname").PersistKeysToAzureBlobStorage(blob);
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.LoginPath = new PathString("/Account/SignIn");
options.Cookie.Name = "somesharedcookiename";
})
.AddOktaMvc(oktaMvcOptions);
我希望这可以帮助那些最终遇到同样问题的人。如果有人可以提出更好的机制来实现这一点,我很想听听您的想法和建议。