Cookies .net core 2.1 cookie身份验证不起作用Sustasys SAML2
这个问题涉及到 我发现,通过SP发起的登录,IdP会发布到Saml2/Acs端点,然后重定向到回调方法,在我的例子中是“SamlLoginCallback”。此方法检查SAML身份验证是否成功,如果成功,则向用户的web浏览器写入cookie。此cookie允许用户访问名为GetLoginDtoSaml的辅助方法,该方法是安全的。这一切都适用于SP启动的工作流 但是,在IdP启动的工作流中,对GetLoginDtoSaml的调用由于缺乏授权而失败。我不明白为什么相同的代码在第一个场景中有效,但在后一个场景中无效 请注意,我确实在我的Chrome浏览器中看到一个名为“.AspNetCore.Cookies”的cookie。但当我尝试转到GetLoginDtoSaml方法时,仍然会得到302重定向。这对我来说毫无意义 以下是代码方法:Cookies .net core 2.1 cookie身份验证不起作用Sustasys SAML2,cookies,asp.net-core-2.1,sustainsys-saml2,Cookies,Asp.net Core 2.1,Sustainsys Saml2,这个问题涉及到 我发现,通过SP发起的登录,IdP会发布到Saml2/Acs端点,然后重定向到回调方法,在我的例子中是“SamlLoginCallback”。此方法检查SAML身份验证是否成功,如果成功,则向用户的web浏览器写入cookie。此cookie允许用户访问名为GetLoginDtoSaml的辅助方法,该方法是安全的。这一切都适用于SP启动的工作流 但是,在IdP启动的工作流中,对GetLoginDtoSaml的调用由于缺乏授权而失败。我不明白为什么相同的代码在第一个场景中有效,但在
[AllowAnonymous]
[HttpPost, HttpGet]
[Route("api/Security/SamlLoginCallback")]
public async Task<IActionResult> SamlLoginCallback(string returnUrl)
{
LogDebugInfo("SamlLoginCallback called with returnUrl of " + returnUrl);
var authenticateResult = await HttpContext.AuthenticateAsync(ApplicationSamlConstants.External);
if (!authenticateResult.Succeeded)
{
LogSamlFailInfo(authenticateResult);
return Unauthorized();
}
// if we get here, we have successful SAML authentication, and should have a username
// (to which we need to add the redirect client ID if configured to user redirect)
var userName = _config.GetValue<bool>("Database:IgnoreRedirect")
? authenticateResult.Principal.FindFirst(ClaimTypes.NameIdentifier).Value.ToString()
: _config.GetValue<string>("Saml:RedirectClientId") + "." + authenticateResult.Principal.FindFirst(ClaimTypes.NameIdentifier).Value.ToString();
// required for SAML logout
// see https://stackoverflow.com/questions/58961868/not-able-to-signout-using-saml2-from-sustainsys
var samlLogoutNameIdentifier = authenticateResult.Principal.GetClaimValue(CustomClaimTypes.SAMLLogoutNameIdentifier);
var samlSessionIndex = authenticateResult.Principal.GetClaimValue(CustomClaimTypes.SAMLSessionIndex);
// set temporary cookie, which will be replaced when client calls GetLoginDtoSaml
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, userName),
new Claim(CustomClaimTypes.SAMLLogoutNameIdentifier, samlLogoutNameIdentifier),
new Claim(CustomClaimTypes.SAMLSessionIndex, samlSessionIndex),
};
ClaimsIdentity userIdentity = new ClaimsIdentity(claims, "login");
ClaimsPrincipal principal = new ClaimsPrincipal(userIdentity);
// https://stackoverflow.com/questions/46243697/asp-net-core-persistent-authentication-custom-cookie-authentication
await Microsoft.AspNetCore.Authentication.AuthenticationHttpContextExtensions
.SignInAsync(HttpContext, CookieAuthenticationDefaults.AuthenticationScheme, principal, new AuthenticationProperties
{
IsPersistent = false,
});
LogDebugInfo("Temporary cookie set in SamlLoginCallback with CookieAuthenticationDefaults.AuthenticationScheme");
if (!string.IsNullOrEmpty(returnUrl))
{
LogDebugInfo("Redirecting to " + returnUrl);
return Redirect(returnUrl);
}
return this.Ok();
}
[HttpPost]
[Route("api/Security/GetLoginDtoSaml")]
[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)]
public async Task<IActionResult> GetLoginDtoSaml()
{
try
{
LogDebugInfo("GetLoginDtoSaml called");
var loginDto = new LoginDto();
loginDto.Username = User.Identity.Name;
loginDto.IsSamlAuthenticated = true;
// pick up claims required for SAML logout
loginDto.SAMLLogoutNameIdentifier = User.GetClaimValue(CustomClaimTypes.SAMLLogoutNameIdentifier);
loginDto.SAMLSessionIndex = User.GetClaimValue(CustomClaimTypes.SAMLSessionIndex);
// Now is when we do a database query to get necessary information for the LoginDTO object
// If the IdP has authenticated the user, but the user does not actually exist in our database,
// this method will throw an error, and we need to log the user out so they can try to login again as a valid user
var dbValidationSuccess = false;
var dbValidationErrorInfo = "";
try
{
dbValidationSuccess = _securitySvc.ValidateLogin(loginDto);
LogDebugInfo("GetLoginDtoSaml - dbValidationSuccess = " + dbValidationSuccess);
}
catch (Exception ex)
{
dbValidationErrorInfo = ex.Message;
LogError(ex);
}
if (dbValidationSuccess)
{
// update cookie with relevant data
await SetCookie(loginDto);
return StatusCode(Microsoft.AspNetCore.Http.StatusCodes.Status200OK, loginDto);
}
else
{
// log user out of the application
LogDebugInfo("GetLoginDtoSaml - logging user out of application");
await Microsoft.AspNetCore.Authentication.AuthenticationHttpContextExtensions
.SignOutAsync(HttpContext, CookieAuthenticationDefaults.AuthenticationScheme);
throw new DisplayException("Despite SAML Authentication from the Identity Provider, user data was not found in the local database. Please refresh the page to retry. Error: " + dbValidationErrorInfo);
}
}
catch (Exception ex)
{
return HandleError(ex);
}
}
[AllowAnonymous]
[HttpPost,HttpGet]
[路由(“api/Security/SamlLoginCallback”)]
公共异步任务SamlLoginCallback(字符串返回URL)
{
LogDebugInfo(“调用SamlLoginCallback时返回URL为“+returnUrl”);
var authenticateResult=等待HttpContext.authenticateSync(applicationsAMLContents.External);
如果(!authenticateResult.successed)
{
LogSamlFailInfo(authenticateResult);
未经授权返回();
}
//如果我们到了这里,我们已经成功地进行了SAML身份验证,并且应该有一个用户名
//(如果配置为用户重定向,则需要向其添加重定向客户端ID)
var userName=\u config.GetValue(“数据库:IgnoreRedirect”)
?authenticateResult.Principal.FindFirst(ClaimTypes.NameIdentifier).Value.ToString()
:_config.GetValue(“Saml:RedirectClientId”)+“+authenticateResult.Principal.FindFirst(ClaimTypes.NameIdentifier.Value.ToString();
//SAML注销时需要
//看https://stackoverflow.com/questions/58961868/not-able-to-signout-using-saml2-from-sustainsys
var samlLogoutNameIdentifier=authenticateResult.Principal.GetClaimValue(CustomClaimTypes.samlLogoutNameIdentifier);
var samlSessionIndex=authenticateResult.Principal.GetClaimValue(CustomClaimTypes.samlSessionIndex);
//设置临时cookie,当客户端调用GetLoginDtoSaml时将替换该cookie
var索赔=新列表
{
新索赔(ClaimTypes.Name、用户名),
新索赔(CustomClaimTypes.SAMLLogoutNameIdentifier,SAMLLogoutNameIdentifier),
新索赔(CustomClaimTypes.SAMLSessionIndex、SAMLSessionIndex),
};
ClaimsIdentity userIdentity=新的ClaimsIdentity(声明“登录”);
ClaimsPrincipal principal=新的ClaimsPrincipal(用户标识);
// https://stackoverflow.com/questions/46243697/asp-net-core-persistent-authentication-custom-cookie-authentication
等待Microsoft.AspNetCore.Authentication.AuthenticationHttpContextensions
.SignInAsync(HttpContext,CookieAuthenticationDefaults.AuthenticationScheme,主体,新AuthenticationProperties
{
ispersist=false,
});
LogDebugInfo(“使用CookieAuthenticationDefaults.AuthenticationScheme在SamlLoginCallback中设置的临时cookie”);
如果(!string.IsNullOrEmpty(returnUrl))
{
LogDebugInfo(“重定向到”+returnUrl);
返回重定向(returnUrl);
}
返回这个;
}
[HttpPost]
[路由(“api/Security/GetLoginDtoSaml”)]
[授权(AuthenticationSchemes=CookieAuthenticationDefaults.AuthenticationScheme)]
公共异步任务GetLoginDtoSaml()
{
尝试
{
LogDebugInfo(“调用GetLoginDtoSaml”);
var loginDto=新的loginDto();
loginDto.Username=User.Identity.Name;
loginDto.IsSamlAuthenticated=true;
//提取SAML注销所需的索赔
loginDto.SAMLLogoutNameIdentifier=User.GetClaimValue(CustomClaimTypes.SAMLLogoutNameIdentifier);
loginDto.SAMLSessionIndex=User.GetClaimValue(CustomClaimTypes.SAMLSessionIndex);
//现在是我们进行数据库查询以获取LoginDTO对象的必要信息的时候了
//如果IdP已经对用户进行了身份验证,但该用户实际上不存在于我们的数据库中,
//此方法将抛出一个错误,我们需要将用户注销,以便他们可以尝试以有效用户身份再次登录
var dbValidationSuccess=false;
var dbValidationErrorInfo=“”;
尝试
{
dbValidationSuccess=\u securitySvc.ValidateLogin(loginDto);
LogDebugInfo(“GetLoginDtoSaml-dbValidationSuccess=“+dbValidationSuccess”);
}
捕获(例外情况除外)
{
dbValidationErrorInfo=ex.消息;
对数误差(ex);
}
if(dbValidationSuccess)
{
//使用相关数据更新cookie
等待SetCookie(loginDto);
返回状态码(Microsoft.AspNetCore.Http.StatusCodes.Status200OK,loginDto);
}
其他的
{
//将用户注销应用程序
LogDebugInfo(“GetLoginDtoSaml-将用户从应用程序中注销”);
等待Microsoft.AspNetCore.Authentication.AuthenticationHttpContextensions
.SignOutAsync(HttpContext,CookieAuthenticationDefaults.AuthenticationScheme);
抛出新的DisplayException(“尽管来自身份提供程序的SAML身份验证,但在本地数据库中找不到用户数据。请刷新页面以重试。错误:“+dbValidationErrorInfo”);
}
}
捕获(例外情况除外)
{
返回手柄错误(ex);
}
}
在我的启动方法中:
public void ConfigureServices(IServiceCollection services)
{
services.AddMemoryCache();
var usingSAML = Configuration.GetValue<bool>("Authentication:UseSAML");
var usingJWT = Configuration.GetValue<bool>("Authentication:UseJWT");
AuthenticationBuilder authBuilder = null;
if (usingSAML)
{
// primary reference for SAML code: https://github.com/hmacat/Saml2WebAPIAndAngularSpaExample
// found via this SO link: https://stackoverflow.com/questions/55025336/sustainsys-saml2-sample-for-asp-net-core-webapi-without-identity
// added to address bug with Okta integration
// see https://www.developreference.com/article/10349604/Sustainsys+SAML2+Sample+for+ASP.NET+Core+WebAPI+without+Identity
// and https://stackoverflow.com/questions/63853661/authenticateresult-succeeded-is-false-with-okta-and-sustainsys-saml2/63890322#63890322
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo("Logs"));
services.Configure<CookiePolicyOptions>(options =>
{
// see https://stackoverflow.com/questions/59742825/httpcontext-signinasync-fails-to-set-cookie-and-return-user-identity-isauthent
options.ConsentCookie.IsEssential = true;
options.CheckConsentNeeded = context => false;
options.MinimumSameSitePolicy = SameSiteMode.None;
// Some older browsers don't support SameSiteMode.None.
options.OnAppendCookie = cookieContext => SameSite.CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
options.OnDeleteCookie = cookieContext => SameSite.CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
});
authBuilder = services.AddAuthentication(o =>
{
o.DefaultScheme = ApplicationSamlConstants.Application;
o.DefaultSignInScheme = ApplicationSamlConstants.External;
o.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
});
authBuilder.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
// see https://stackoverflow.com/questions/46243697/asp-net-core-persistent-authentication-custom-cookie-authentication
options.ExpireTimeSpan = new System.TimeSpan(365, 0, 0, 0, 0);
options.AccessDeniedPath = new PathString("/login");
options.LoginPath = new PathString("/login");
// see https://stackoverflow.com/questions/59742825/httpcontext-signinasync-fails-to-set-cookie-and-return-user-identity-isauthent
options.Cookie.IsEssential = true;
options.Cookie.HttpOnly = true;
//options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.Cookie.SameSite = SameSiteMode.None;
})
.AddCookie(ApplicationSamlConstants.Application)
.AddCookie(ApplicationSamlConstants.External)
.AddSaml2(options =>
{
options.SPOptions.EntityId = new EntityId(this.Configuration["Saml:SPEntityId"]);
var allowIdpInitiated = Configuration.GetValue<bool>("Saml:AllowIdPInitiated");
if (allowIdpInitiated)
{
var siteRoot = this.Configuration["Saml:SiteRoot"];
var siteRootEncoded = WebUtility.UrlEncode(siteRoot + "?idp=y"); // add idp parm so javascript knows what's going on
var returnUrl = string.Format("{0}/api/Security/SamlLoginCallback?returnUrl={1}", siteRoot, siteRootEncoded);
options.SPOptions.ReturnUrl = new System.Uri(returnUrl);
}
options.IdentityProviders.Add(
new IdentityProvider(
new EntityId(this.Configuration["Saml:IDPEntityId"]), options.SPOptions)
{
MetadataLocation = this.Configuration["Saml:IDPMetaDataBaseUrl"],
LoadMetadata = true,
AllowUnsolicitedAuthnResponse = allowIdpInitiated,
});
options.SPOptions.ServiceCertificates.Add(new X509Certificate2(this.Configuration["Saml:CertificateFileName"]));
});
}
public void配置服务(IServiceCollection服务)
{
services.AddMemoryCache();
var usingSAML=Configuration.GetValue(“身份验证:UseSAML”);
var usingJWT=Configuration.GetValue(“身份验证:UseJWT”);
AuthenticationBuilder authBuilder=null;
如果(使用SAML)
{
//SAML代码的主要参考:https://github.com/hmacat/Saml2WebAPIAndAngularSpaExample
//通过此SO链接找到:https://stackoverflow.com/questions/55025336/sustainsys-saml2-sample-for-asp-net-core-webapi-without-identity
//添加了Okta集成以解决错误