无法使用 Sustainsys 的 Saml2 注销

问题描述 投票:0回答:4

这应该将我的应用程序重定向到我的 AdFs 注销页面,然后将我重定向回我的应用程序。 然而,它只是将我重定向到我的路线“/logout”。 查看我的 ADFS 服务器上的日志没有任何反应。

        [AllowAnonymous]
        [HttpGet]
        [Route("api/logout")]
        public async Task<IActionResult> Logout()
        {
            return SignOut(new AuthenticationProperties()
            {
                RedirectUri = "/logout"
            },
            Saml2Defaults.Scheme);
        }

登录工作正常。我什至尝试过同样的方法,但不起作用。这里,ReturnUrl 方法从 HttpContext.Response.Header 获取位置。当我尝试注销时,该位置始终为空。

        [AllowAnonymous]
        [HttpGet]
        [Route("api/login")]
        public async Task<string> LoginAdfs()
        {

            string redirectUri =  _appSettings.Saml.SpEntityId;

            await HttpContext.ChallengeAsync(new AuthenticationProperties
            {
                RedirectUri = string.Concat(redirectUri, "/autenticado")
            });
            return ReturnUrl();
        }

知道会发生什么吗?

更新2019年11月21日

结果 Saml2Handler 根本就没有尝试将请求发送到服务器。我在输出窗口中收到这些消息:

Sustainsys.Saml2.AspNetCore2.Saml2Handler: Debug: Initiating logout, checking requirements for federated logout
  Issuer of LogoutNameIdentifier claim (should be Idp entity id): 
  Issuer is a known Idp: False
  Session index claim (should have a value): 
  Idp has SingleLogoutServiceUrl: 
  There is a signingCertificate in SPOptions: True
  Idp configured to DisableOutboundLogoutRequests (should be false): 
Sustainsys.Saml2.AspNetCore2.Saml2Handler: Information: Federated logout not possible, redirecting to post-logout

这是我的启动配置,我不明白这里出了什么问题:

            ServiceCertificate se = new ServiceCertificate()
            {
                Certificate = new X509Certificate2(SpCert, "",X509KeyStorageFlags.MachineKeySet),
                Use = CertificateUse.Signing
            };

            SPOptions sp = new SPOptions
            {
                AuthenticateRequestSigningBehavior = SigningBehavior.Never,
                EntityId = new EntityId(SpEntityId),
                ReturnUrl = new Uri("/login"),
                NameIdPolicy = new Sustainsys.Saml2.Saml2P.Saml2NameIdPolicy(null, Sustainsys.Saml2.Saml2P.NameIdFormat.Unspecified),

            };
            sp.ServiceCertificates.Add(se);

            IdentityProvider idp = new IdentityProvider(new EntityId(appSettings.Saml.EntityId), sp);
            idp.Binding = Saml2BindingType.HttpPost;
            idp.AllowUnsolicitedAuthnResponse = true;
            //idp.WantAuthnRequestsSigned = true;
            idp.SingleSignOnServiceUrl = new Uri("/login");
            //idp.LoadMetadata = true;
            idp.SigningKeys.AddConfiguredKey(new X509Certificate2(IdpCert));
            idp.MetadataLocation = theMetadata;
            idp.DisableOutboundLogoutRequests = true;

asp.net-core saml saml-2.0 sustainsys-saml2
4个回答
5
投票

为了使注销正常工作,用户需要有两个特殊声明“LogoutNameIdentifier”和“SessionIndex”(全名是

http://Sustainsys.se/Saml2/LogoutNameIdentifier
http://Sustainsys.se/Saml2/SessionIndex
)。它们携带有关 Saml2 库需要的当前会话的信息。能够注销。

现在我看不到你的整个启动,所以我无法理解你的应用程序的流程。但这些声明应该出现在库返回的身份中 - 可能存储在外部 cookie 中(如果您使用的是 asp.net 身份)。当您的应用程序设置应用程序 cookie 时,这两个声明必须转移到会话身份。

此外,您实际上已使用

DisableOutboundLogoutRequests
禁用了出站注销。但这不是这里的主要问题,因为您的日志表明所需的声明不存在。


1
投票

根据我自己的经验,Anders Abel 提到的这两个声明应该出现在用户身上。直到我通过了所有声明以及登录请求后,我才看到这些声明。 ASP.NET Core 在

SignInAsync
上重新创建主体,并需要随请求传入声明。

通过以下操作,我可以通过我的服务实现 SingleLogout:

await HttpContext.SignInAsync(user.SubjectId, user.Username, props, user.Claims.ToArray());

0
投票

在为此苦苦挣扎了一段时间之后,这里是实现注销功能的代码,作为 Saml2WebAPIAndAngularSpaExample 的扩展(请参阅https://github.com/hmacat/Saml2WebAPIAndAngularSpaExample)。

Startup.cs

builder.Services.AddAuthentication(o =>
{
    o.DefaultScheme = ApplicationSamlConstants.Application;
    o.DefaultSignInScheme = ApplicationSamlConstants.External;
    o.DefaultAuthenticateScheme = ApplicationSamlConstants.Application; //needed for logout
})
.AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters()
    {
        ValidateIssuer = false,
        ValidateAudience = false,
        ValidateLifetime = true,
        ValidateIssuerSigningKey = true,
        ClockSkew = TimeSpan.FromMinutes(Convert.ToDouble(builder.Configuration["Jwt:ExpireInMinutes"])),
        ValidIssuer = builder.Configuration["Jwt:Issuer"],
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])),
    };
})
.AddCookie(ApplicationSamlConstants.Application)
.AddCookie(ApplicationSamlConstants.External)
.AddSaml2(options =>
{
    options.SPOptions.EntityId = new EntityId(builder.Configuration["Saml:SPEntityId"]);
    var idp = new IdentityProvider(new EntityId(builder.Configuration["Saml:IDPEntityId"]), options.SPOptions)
    {
        Binding = Saml2BindingType.HttpPost,
        SingleSignOnServiceUrl = new Uri(builder.Configuration["Saml:IDPSingleSignOnServiceUrl"]),
        SingleLogoutServiceBinding = Saml2BindingType.HttpRedirect,
        SingleLogoutServiceUrl = new Uri(builder.Configuration["Saml:IDPSingleLogoutServiceUrl"]),
        AllowUnsolicitedAuthnResponse = true,
        DisableOutboundLogoutRequests = false,
        //LoadMetadata = false
    };
    idp.SigningKeys.AddConfiguredKey(new X509Certificate2(builder.Configuration["Saml:IDPCertificateFileName"]));
    options.IdentityProviders.Add(idp);
    options.SPOptions.ServiceCertificates.Add(new X509Certificate2(builder.Configuration["Saml:SPCertificateFileName"]));
});

    需要将
  • DefaultAuthenticateScheme
    设置为
    ApplicationSamlConstants.Application
    方案才能使注销正常工作。控制器中的
    Authorize
    属性需要显式选择方案
    JwtBearerDefaults.AuthenticationScheme
    - 见下文。
  • 需要设置
  • Sustainsys.Saml2
    参数
    SingleLogoutServiceBinding
    SingleLogoutServiceUrl
    DisableOutboundLogoutRequests

SamlController.cs

[AllowAnonymous]
[HttpGet("InitiateSingleSignOn")]
public IActionResult InitiateSingleSignOn(string returnUrl)
{
    // challenge the user to sign in using SAML
    return new ChallengeResult(
        Saml2Defaults.Scheme,
        new AuthenticationProperties
        {
            RedirectUri = Url.Action(nameof(LoginCallback), new { returnUrl })
        });
}

[AllowAnonymous]
[HttpGet("Callback")]
public async Task<IActionResult> LoginCallback(string returnUrl)
{
    // authenticate and sign in
    // (see https://stackoverflow.com/questions/53654020/how-to-implement-google-login-in-net-core-without-an-entityframework-provider for details)
    var authenticateResult = await HttpContext.AuthenticateAsync(ApplicationSamlConstants.External);

    if (!authenticateResult.Succeeded)
    {
        return Unauthorized();
    }

    var claimsIdentity = new ClaimsIdentity(ApplicationSamlConstants.Application);

    claimsIdentity.AddClaim(authenticateResult.Principal.FindFirst(ClaimTypes.NameIdentifier));
    claimsIdentity.AddClaim(authenticateResult.Principal.FindFirst("http://Sustainsys.se/Saml2/LogoutNameIdentifier"));
    claimsIdentity.AddClaim(authenticateResult.Principal.FindFirst("http://Sustainsys.se/Saml2/SessionIndex"));

    await HttpContext.SignInAsync(
        ApplicationSamlConstants.Application,
        new ClaimsPrincipal(claimsIdentity));


    // issue JWT token
    var token = this.CreateJwtSecurityToken(authenticateResult);
    HttpContext.Session.SetString("JWT", new JwtSecurityTokenHandler().WriteToken(token));


    // redirect to the page that caused our first challenge
    if (!string.IsNullOrEmpty(returnUrl))
    {
        return Redirect(returnUrl);
    }

    return this.Ok();
}


[AllowAnonymous]
[HttpGet("InitiateSingleLogout")]
public IActionResult InitiateSingleLogout(string returnUrl)
{
    HttpContext.Session.SetString("JWT", "");

    return SignOut(
        new AuthenticationProperties()
        {
            RedirectUri = Url.Action(nameof(LogoutCallback), new { returnUrl })
        },
        ApplicationSamlConstants.External,
        ApplicationSamlConstants.Application,
        Saml2Defaults.Scheme
    );
}

[AllowAnonymous]
[HttpGet("LogoutCallback")]
public IActionResult LogoutCallback(string returnUrl)
{
    if (!string.IsNullOrEmpty(returnUrl))
    {
        return Redirect(returnUrl);
    }

    return this.Ok();
}

AuthorizationController.cs

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[HttpGet("TestAuthorization")]
public ActionResult TestAuthorization()
{
    return this.Ok("Congratulations, you are authorized.");
}

-2
投票

您作为服务提供商正在使用什么。

© www.soinside.com 2019 - 2024. All rights reserved.