.net Core 2.1 Cookie身份验证无法正常工作sustainsys SAML2

时间:2020-09-18 00:29:48

标签: cookies asp.net-core-2.1 sustainsys-saml2

这个问题与IdP Initiated Login with Sustainsys.SAML2 - AuthenticateResult Has No Information有关。

我发现,通过SP发起的登录,IdP将发布到Saml2 / Acs端点,然后该端点将重定向到回调方法,在我的情况下为“ SamlLoginCallback”。此方法检查SAML身份验证是否成功,如果成功,则将cookie写入用户的Web浏览器。该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);
  }
}

在我的启动方法中:

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"]));
      
    });
  }

}

1 个答案:

答案 0 :(得分:0)

最终,这里的问题是,用于写入然后读取Cookie的Cookie路径中的大小写不一致。因此,在编写cookie时,无法读取它。