У нас есть следующая среда:
- Реализация IdentityServer4 в качестве поставщика услуг идентификации.
- 2 Сайты, созданные на основе. Net Core 2.2.8 и интегрированные с Identity .
- Локальный IIS 10.0.18362.1 на моем ноутбуке Windows 10 Pro
Конфигурация клиента на IdentityServer:
new Client
{
ClientName = "App1",
ClientId = "App1",
AllowedGrantTypes = GrantTypes.Hybrid,
RequireConsent = false,
RedirectUris = "https://localhost:5000/siging-oidc",
PostLogoutRedirectUris = "https://localhost:5000/home/index",
FrontChannelLogoutUri = "https://localhost:5000/home/frontchannellogout",
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile
},
ClientSecrets =
{
new Secret("secretApp1".Sha256())
}
},
new Client
{
ClientName = "App2",
ClientId = "App2",
AllowedGrantTypes = GrantTypes.Hybrid,
RequireConsent = false,
RedirectUris = "https://localhost:5001/siging-oidc",
PostLogoutRedirectUris = "https://localhost:5000/home/index",
FrontChannelLogoutUri = "https://localhost:5001/home/frontchannellogout",
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile
},
ClientSecrets =
{
new Secret("secretApp2".Sha256())
}
}
Конфигурация клиента на стороне клиента:
//For authentication App1
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "oidc";
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = "https://localhost:44329/";
options.ClientId = "App1";
options.ResponseType = "code id_token";
options.Scope.Add(OidcConstants.StandardScopes.OpenId);
options.Scope.Add(OidcConstants.StandardScopes.Profile);
options.SaveTokens = true;
options.ClientSecret = "secretApp1";
options.GetClaimsFromUserInfoEndpoint = true;
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = JwtClaimTypes.GivenName
};
options.ClaimActions.Remove("acr");
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = n =>
{
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.Authentication)
{
n.ProtocolMessage.AcrValues = $"tenant:{configuration["TenantId"]}";
}
return Task.FromResult(0);
},
OnRemoteFailure = context =>
{
context.Response.Redirect("/home");
context.HandleResponse();
return Task.FromResult(0);
}
};
});
//For authentication App2
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "oidc";
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = "https://localhost:44329/";
options.ClientId = "App2";
options.ResponseType = "code id_token";
options.Scope.Add(OidcConstants.StandardScopes.OpenId);
options.Scope.Add(OidcConstants.StandardScopes.Profile);
options.SaveTokens = true;
options.ClientSecret = "secretApp2";
options.GetClaimsFromUserInfoEndpoint = true;
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = JwtClaimTypes.GivenName
};
options.ClaimActions.Remove("acr");
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = n =>
{
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.Authentication)
{
n.ProtocolMessage.AcrValues = $"tenant:{configuration["TenantId"]}";
}
return Task.FromResult(0);
},
OnRemoteFailure = context =>
{
context.Response.Redirect("/home");
context.HandleResponse();
return Task.FromResult(0);
}
};
});
Это реализации Logout для обоих приложений:
// Logout App1
public async Task<IActionResult> Logout()
{
if (User.Identity.IsAuthenticated)
{
var idToken = await HttpContext.GetTokenAsync("id_token");
HttpContext.Session.Clear();
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
var disco = await _discoveryCache.GetAsync();
var postLogoutUrl = "https://localhost:5001/home/index";// I want to go to App2 when I logout from App1
var urlEndSession = new RequestUrl(disco.EndSessionEndpoint).CreateEndSessionUrl(idToken, postLogoutUrl);
return Redirect(urlEndSession);
}
return View("Index");
}
// Logout App2
public async Task<IActionResult> Logout()
{
if (User.Identity.IsAuthenticated)
{
var idToken = await HttpContext.GetTokenAsync("id_token");
HttpContext.Session.Clear();
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
var disco = await _discoveryCache.GetAsync();
var postLogoutUrl = "https://localhost:5000/home/index"; // I want to go to App1 when I logout from App2
var urlEndSession = new RequestUrl(disco.EndSessionEndpoint).CreateEndSessionUrl(idToken, postLogoutUrl);
return Redirect(urlEndSession);
}
return View("Index");
}
Это реализации FrontChannelLogout для обоих приложений:
[Authorize]
public async Task<IActionResult> Frontchannellogout(string sid)
{
if (User.Identity.IsAuthenticated)
{
var currentSid = User.FindFirst("sid")?.Value ?? "";
if (string.Equals(currentSid, sid, StringComparison.Ordinal))
{
HttpContext.Session.Clear();
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}
}
return NoContent();
}
Мы работаем над выполнением sh, что когда пользователь выходит из одного приложения, он фактически выходит из всей системы, используя спецификацию фронтального канала.
Как только вы входите в приложение App1 и переходите в приложение App2, у нас есть SSO и пользователь вошел в оба приложения. Как только пользователь решит выйти из текущего приложения, он должен выйти из другого приложения и Identity Server.
Приложение (App1), из которого я вышел, удалило куки, как это должно было произойти, и куки-файлы identityServer также были удалены, но другое приложение (App2) не удаляло свои куки-файлы и никогда не запускало свой frontchannellogout, поэтому я все еще вхожу в это второе приложение (App2).
I Мне известно о недавних изменениях, связанных с развертыванием Google, связанных с параметром SameSite cook ie, и я также получил обновленную информацию, связанную с недавним откатом этих изменений, и даже подумал, что не уверен на 100%, что является причиной моей проблемы, я думаю, это может быть связано с этим. Из-за этого я попытался обойти несколько решений, которые я нашел в Интернете, но пока у меня ничего не получилось,
Я использовал это Предстоящий SameSite Cook ie Изменения в ASP. NET и ASP. NET Core в качестве руководства для решения моей проблемы. Это мой код для классов StartUp.cs App1 и App2:
private void CheckSameSite(HttpContext httpContext, CookieOptions options)
{
if (options.SameSite == SameSiteMode.None)
{
var userAgent = httpContext.Request.Headers["User-Agent"].ToString();
if (userAgent.Contains("Chrome/8") || userAgent.Contains("Firefox/7"))
{
options.SameSite = (SameSiteMode)(-1);
}
}
}
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.MinimumSameSitePolicy = (SameSiteMode)(-1);
options.OnAppendCookie = cookieContext =>
CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
options.OnDeleteCookie = cookieContext =>
CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
});
// ... some configurations more like services.AddAuthentication(...) ...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// ...
app.UseCookiePolicy();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
РЕДАКТИРОВАТЬ: браузер готовить ie хранилище
1-) войти в App1
2-) войти в App2
3-) после нажатия первый шаг из App2
4-) после перенаправления identityserver на App1, поскольку postlogoutredirecturi - это App1 в App2