azure - 身份服务器4,本地登录/天蓝色的不同行为
问题描述
环境:localhost / azure,.netcore 3.1 mvc identityserver4 + mvc api 客户端。
当我在本地运行我的应用程序时,登录/注销工作正常,有: - identityserver4 mvc .netcore 3.1 - client mvc api .netcore 3.1
我可以随心所欲地登录/注销,登录始终重定向到 identityserver4 登录并且登录有效。
当与 Azure 上托管的 identityserver4 相同的应用程序第一次登录正确地重定向到 azure identityserver4,并且登录工作正常。然后在注销后(似乎已删除 cockies),当我再次尝试登录时,重定向到登录页面不起作用,并且存在“隐式”登录并直接重定向到网站主页。
客户端 mvc api 配置如下:
{
"ClientId": "IdentityServer.WebApi",
"ClientSecret": "IdentityServer.WebApi",
"AllowedGrantTypes": "GrantTypes.CodeAndClientCredentials",
"RedirectUris": [
"https://localhost:44372/signin-oidc",
"https://localhost:5001/signin-oidc",
"https://192.168.1.7:44372/signin-oidc",
"https://mogui:44372/signin-oidc"
],
"PostLogoutRedirectUris": [
"https://localhost:44372/signout-callback-oidc",
"https://localhost:5001/signout-callback-oidc",
"https://192.168.1.7:44372/signout-callback-oidc",
"https://mogui:44372/signout-callback-oidc"
],
"AllowedScopes": [
"openid",
"profile"
],
"RequireConsent": true,
"RequirePkce": true,
"AllowOfflineAccess": true
},
本地/天蓝色的 identityserver4 在其 Startup 类上有这种代码:
public void ConfigureServices(IServiceCollection services)
{
try
{
telemetryClient.TrackTrace("============== Startup ConfigureServices ============== ");
// uncomment, if you wan to add an MVC-based UI
services.AddControllersWithViews();
//services.AddMvc();
string connectionString = Configuration.GetConnectionString("IdentityDbContextConnection");
//const string connectionString = @"Data Source=(LocalDb)\MSSQLLocalDB;database=IdentityServer4.Quickstart.EntityFramework-3.0.102;trusted_connection=yes;";
var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
services.AddDbContext<IdentityServer.Models.IdentityDbContext>(options =>
options.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly))
);
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.SignIn.RequireConfirmedEmail = true;
})
.AddEntityFrameworkStores<IdentityServer.Models.IdentityDbContext>()
.AddDefaultTokenProviders();
services.AddMvc(options =>
{
options.EnableEndpointRouting = false;
})
.SetCompatibilityVersion(CompatibilityVersion.Latest);
var builder = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
options.UserInteraction.LoginUrl = "/Account/Login";
options.UserInteraction.LogoutUrl = "/Account/Logout";
options.Authentication = new AuthenticationOptions()
{
CookieLifetime = TimeSpan.FromHours(10), // ID server cookie timeout set to 10 hours
CookieSlidingExpiration = true
};
})
.AddSigningCredential(X509.GetCertificate("B22BBE7C991CEF13F470481A4042D1E091967FCC")) // signing.crt thumbprint
.AddValidationKey(X509.GetCertificate("321ABA505F6FCDDD00AA5EC2BD307F0C9002F9A8")) // validation.crt thumbprint
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = b => b.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly));
})
.AddOperationalStore(options =>
{
options.ConfigureDbContext = b => b.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly));
options.EnableTokenCleanup = true;
})
.AddAspNetIdentity<ApplicationUser>();
services.AddAuthentication()
.AddGoogle("Google", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.ClientId = "174637674775-7bgu471gtme25sr5iagq5agq6riottek.apps.googleusercontent.com";
options.ClientSecret = "V_UsR825ZWxCB9i2xrN-u1Kj";
});
services.AddTransient<IEmailSender, IdentityEmailSender>();
services.AddCors(options => options.AddPolicy("AllowAll", p => p.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()));
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.Strict;
});
services.AddScoped<IProfileService, ProfileService>();
telemetryClient.TrackTrace("============== Startup ConfigureServices finish OK ============== ");
}
catch (Exception e)
{
telemetryClient.TrackTrace("Exception general in ConfigureServices");
telemetryClient.TrackException(e);
throw;
}
}
和这个 :
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
try
{
telemetryClient.TrackTrace("============== Startup Configure ============== ");
InitializeDatabase(app);
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.UseCors("AllowAll");
app.UseRouting();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
app.UseMvcWithDefaultRoute();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
telemetryClient.TrackTrace("============== Startup Configure finish OK============== ");
}
catch (Exception e)
{
telemetryClient.TrackTrace("Exception general in Configure");
telemetryClient.TrackException(e);
throw;
}
}
所以问题在于
identityserver4 localhost 登录/注销工作找到
托管在 azure 上的 idnetityserver4 登录被跳过并直接进入主页(用户通过以前的登录进行身份验证)。
抱歉有点长,我在stackoverflow或其他地方没有看到这个确切的问题。
提前谢谢!
解决方案
你说得对,根据我看到的不同帖子,我们可以做这样的事情:
- first of all, we have to put FrontChannelLogoutUri parameter
(它应该是一个由 identityserver4 调用的 mvc 客户端控制器/动作,在我们的例子中应该类似于https://localhost:999/Account/FrontChannelLogout)用于客户端 mvc 应用程序,通常它放在 Config.cs 中并添加这个客户端 Mvc 的参数(使用 RedirectUris、PostLogoutRedirectUris、...)
- on the client mvc, in an account controller (for instance) where is managed the login ,
我们可以添加/修改注销管理:
[Authorize]
public async Task<IActionResult> Logout()
{
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync($"https://{Startup.Configuration["Auth0:Domain"]}");
return Redirect(disco.EndSessionEndpoint);
}
public async Task<IActionResult> FrontChannelLogout(string sid)
{
if (User.Identity.IsAuthenticated)
{
var currentSid = User.FindFirst("sid")?.Value ?? "";
if (string.Equals(currentSid, sid, StringComparison.Ordinal))
{
await HttpContext.SignOutAsync("oidc");
await HttpContext.SignOutAsync("Identity.Application");
await _signInManager.Context.SignOutAsync("_af");
await _signInManager.Context.SignOutAsync("idsrv.session");
await _signInManager.Context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}
}
return NoContent();
}
在 identityserver4 方面:
在 QuickStart Account Controller 中,我们必须更新 BuildLoggedOutViewModelAsync 方法:
private async Task<LoggedOutViewModel> BuildLoggedOutViewModelAsync(string logoutId)
{
// get context information (client name, post logout redirect URI and iframe for federated signout)
var logout = await _interaction.GetLogoutContextAsync(logoutId);
var client = await _clientStore.FindEnabledClientByIdAsync(logout.ClientIds.First());
if (!string.IsNullOrEmpty(client.FrontChannelLogoutUri))
{
//var pos = GetNthIndex(client.FrontChannelLogoutUri, '/', 3);
//logout.PostLogoutRedirectUri = client.FrontChannelLogoutUri.Substring(0, Math.Min(client.FrontChannelLogoutUri.Length, pos));
// Here TODO =====> get the real PostLogoutRedirectUri, it should be a controller/action url on the client mvc side and put it in **logout.PostLogoutRedirectUri**
}
var vm = new LoggedOutViewModel
{
AutomaticRedirectAfterSignOut = AccountOptions.AutomaticRedirectAfterSignOut,
PostLogoutRedirectUri = logout?.PostLogoutRedirectUri,
ClientName = string.IsNullOrEmpty(logout?.ClientName) ? logout?.ClientId : logout?.ClientName,
SignOutIframeUrl = logout?.SignOutIFrameUrl,
LogoutId = logoutId
};
if (User?.Identity.IsAuthenticated == true)
{
var idp = User.FindFirst(JwtClaimTypes.IdentityProvider)?.Value;
if (idp != null && idp != IdentityServer4.IdentityServerConstants.LocalIdentityProvider)
{
var providerSupportsSignout = await HttpContext.GetSchemeSupportsSignOutAsync(idp);
if (providerSupportsSignout)
{
if (vm.LogoutId == null)
{
// if there's no current logout context, we need to create one
// this captures necessary info from the current logged in user
// before we signout and redirect away to the external IdP for signout
vm.LogoutId = await _interaction.CreateLogoutContextAsync();
}
vm.ExternalAuthenticationScheme = idp;
}
}
}
return vm;
}
====> 显然 _interaction.GetLogoutContextAsync(logoutId) 永远不会返回 PostLogoutRedirectUri,即使它已为 mvc 客户端(在 Config.cs 中)设置。
====> 通过在 identityServer4 端填写此参数 logout.PostLogoutRedirectUri ,它将注销重定向到客户端应用程序。
这就是我能说的,我不知道注销重定向到客户端应用程序是否是“标准”行为,不知道它是否是在identityserver4中计划的。
一些链接:
https://andersonnjen.com/2019/03/22/identityserver4-global-logout/ 从身份服务器注销后如何将用户重定向到客户端应用程序?
谢谢!
推荐阅读
- java - rxJava 2.0 如何将两个 sql 表的结果合并到一个 JsonObject 中
- junit - MyBatis - 测试参数 JdbcType
- scala - 如何将 Array[String] 转换为 spark Dataframe 以保存 CSV 文件格式?
- splunk - DB Connect 任务服务器未连接
- javascript - 如何在浏览器中实现 HTTP/2 流连接?
- sql - 如何从只读 SQL 数据库中提取信息?
- sql - PySpark / Spark 窗口功能第一期/最后一期
- reactjs - 如何禁用列表中的单个按钮?
- angular - Angular - 从外键获取对象
- sql-server - DATEADD(DAY, 1, GETDATE()) 和 DATEADD(DAY, 1, DATEDIFF(DAY, 0, GETDATE())) 有什么不同