Cookies .net core 2.1 cookie身份验证不起作用Sustasys SAML2

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的调用由于缺乏授权而失败。我不明白为什么相同的代码在第一个场景中有效,但在

这个问题涉及到

我发现,通过SP发起的登录,IdP会发布到Saml2/Acs端点,然后重定向到回调方法,在我的例子中是“SamlLoginCallback”。此方法检查SAML身份验证是否成功,如果成功,则向用户的web浏览器写入cookie。此cookie允许用户访问名为GetLoginDtoSaml的辅助方法,该方法是安全的。这一切都适用于SP启动的工作流

但是,在IdP启动的工作流中,对GetLoginDtoSaml的调用由于缺乏授权而失败。我不明白为什么相同的代码在第一个场景中有效,但在后一个场景中无效

请注意,我确实在我的Chrome浏览器中看到一个名为“.AspNetCore.Cookies”的cookie。但当我尝试转到GetLoginDtoSaml方法时,仍然会得到302重定向。这对我来说毫无意义

以下是代码方法:

[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集成以解决错误