Asp.Net Core 3 中的用户身份验证,使用 Saml2 和 Okta 作为服务提供者

User authentication in Asp.Net Core 3 with Saml2 and Okta as Service Provider

我正在用 Okta 替换我们内部发明的身份验证服务,并且需要在我们的应用程序中支持使用 Saml2 的 SSO。 Okta 可以配置为服务提供商,发送 SAMLRequest 并接收和验证 SAMLResponse。到目前为止一切顺利:我能够在 Okta 中配置身份提供者,接收断言,并且根据日志,断言已成功验证并自动配置用户。

但是从现在开始会发生什么?

  1. 在 Idp 发起的流程中,如何将用户重定向到应用程序 url? RelayState 由 Idp 配置是唯一的选择吗?如果我们更改我们的应用程序 url,我不喜欢我们所有的客户都必须进行更改的想法。我期望 link Idp 到 Okta 应用程序的可能性,因此重定向 url 将由该应用程序配置。
  2. 重定向到我们的应用程序站点后,如何在 Asp.Net Core 中对用户进行身份验证?我实现了自定义 AuthenticationHandler,它读取 Okta 在重定向时设置的 sid cookie,并从 Okta 检索会话信息。从那里我得到用户名并创建 Principal。这种方法有效,但看起来我错了 - 它太手动了,我希望 Okta.Sdk 为我这样做(如果这是正确的身份验证方法)。
  3. 断言验证成功后,是否可以将 saml 令牌交换为 OAuth 令牌以在应用程序中进行身份验证?

基于纯 SAML 方法回答您的问题:

  1. 您可以使用 SP-initiated SSO - 您将实现登陆某个页面的相同目标。假设您的应用程序充当 SAML 服务提供者驻留在 my.app.com,并且您希望用户在通过身份验证后登陆 https://my.app.com/foo/bar。将 https://my.app.com/foo/bar 发布到您的 clients/users 将导致 SP-init 流在他们最终单击此 link 时启动。在 IdP 和 SP 之间经过一些 back-and-forth 之后,用户将登陆 https://my.app.com/foo/bar 作为他们的最终目的地。

您可以通过应用中的附加重定向或堆栈中应用上游的 IdP-initiated 流程实现相同的想法。这有时被称为 虚荣心 URL。例如,如果 vanity URL 是 https://my.app.com/home,您的应用程序或其他上游组件可以将此 URL 转换为另一个目标 URL 并发出指向目标的重定向。目标 URL 可以启动带有 RelayState 的 IdP-initiated 流或如上所述的 SP-initiated 流。您的应用程序或上游组件必须维护 vanity 和目标 URLs.

之间的映射
  1. 您希望 Okta 成为您的 SAML 身份提供者,而您的应用程序成为 SAML 服务提供者。您尝试了此 cookie-based 解决方法,因为您不了解如何在您的 .NET 应用程序中实施 SAML。

Okta 不为充当 SAML 服务提供商的 .NET 应用程序提供 SAML 工具包或 SDK,他们推荐第 3 方库。 ASP.NET 不支持开箱即用的 SAML……这是 Okta 推荐第 3 方的另一个原因。 (Microsoft 不是这些第 3 方之一)。两个流行的、免费的和广泛使用的选择是 Sustainsys and ITFoxTec。看看他们的文档,选择一个并实施它。在那之后你不应该诉诸你的 cookie 解决方法。

  1. 在您的应用程序处理 SAML 响应并生成回复(如果有)后,身份验证步骤完成并且主体的身份已知。您的应用程序现在可以做任何您想做的下一步,包括获取 oAuth 令牌。下一步不是 SAML 协议的一部分...除非它是 fresh/new SAML 流,即。

根据您使用 Okta 作为 SAML 服务提供者并使用另一个 SAML 身份提供者进行身份存储的方法回答您的问题。流程是 2 个跃点:SAML IdP -> Okta 作为 SAML SP -> 你的应用程序。

您的应用必须通过 front-channel 回调 (SAML-like) 或 API 回调 (oAuth-like) integrate with Okta。后者或多或少需要前者,尽管这条路径有很多变化。下面的答案假定集成的 front-end 回调样式。

  1. 您的应用程序将通过 RelayState“调用”,方法是让 Okta 在处理来自第一跳的 SAML 响应后重定向到 RelayState 中的 URL。如果您将 RelayState 设置为 https://my.app.com/foo/bar,那么在 IdP 和 SP 与您的应用程序之间经过一些 back-and-forth 之后,用户将登陆 https://my.app.com/foo/bar 作为他们的最终目的地。您的应用程序必须通过 IdP-initiated 或 SP-initiated SAML 流激活第一跳来触发此序列。

  2. 在集成的 front-end 回调样式中,您将使用 Microsoft 接口 + Okta 库,如 Okta 的 example.

    所示
  3. 使用 Okta 作为您的身份存储和经过身份验证的委托人,您可以通过其他步骤从 Okta 获取令牌。如果您的目标是通过 Okta 从第 3 方启用 API 调用您的应用程序,请查看 API callback style of integration.

终于,我能够让它按照我想要的方式工作了。目前我遇到 Idp-initiated 流程的问题,如果我找到解决方案,我会更新答案。

  1. 首先配置您组织的 Okta as Saml Sp for external Saml IdP. Api documentation is here. UI instructions are here。您应该从外部 IdP 收到 SAML PROTOCOL SETTINGS(IdP 颁发者 URI、IdP 单一 Sign-On URL 和 IdP 签名证书)的信息。

  2. 下一步在 Okta 中创建 Web application。你的 Login redirect URIs 应该以 /authorization-code/callback 结束,否则你将不得不在代码中的 CallbackPath 属性 中配置它(见下文)。您不需要实现此端点,框架会为您完成。

  3. 现在安装 Okta.AspNetCore nuget 并在 ConfigureServices 方法中添加此代码

     services.AddAuthentication(options =>
     {
         options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
         options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
         options.DefaultChallengeScheme = OktaDefaults.MvcAuthenticationScheme;
     })
     .AddCookie()
     .AddOktaMvc(new OktaMvcOptions
     {
         OktaDomain = "<okta_domain>",
         ClientId = "<app_client_id>",
         ClientSecret = "<app_client_secret>",
         Scope = OktaDefaults.Scope,
         //CallbackPath = "/authorization-code/callback" <= it's default value, change it if required
     });
    

您会在步骤 2 中创建的 Web 应用程序的常规选项卡底部找到 app_client_idapp_client_secret

  1. 现在是最有趣的部分。如果你想为内部用户(不是外部 Saml Idp 用户)触发身份验证过程,你可以调用 ChallengeAsync 方法。像这样:

     [ApiController]
     [Route("[controller]")]
     public class AuthController : ControllerBase
     {
         [HttpGet("login")]
         public async Task Login()
         {
             var properties = new AuthenticationProperties
             {
                 RedirectUri = "/"
             };
    
             await HttpContext.ChallengeAsync(properties);
         }
     }
    

每当您点击 /auth/login url,您将被重定向到 Okta 进行身份验证。

当您想要为外部 Idp 用户触发身份验证过程时,您应该在 AuthenticationProperties class.

中添加 idp 参数
[ApiController]
[Route("[controller]")]
public class SsoController : ControllerBase
{
    private readonly ILogger<SsoController> _logger;

    private readonly IOktaClient _oktaClient;

    public SsoController(ILogger<SsoController> logger, IOktaClient oktaClient)
    {
        _logger = logger;
        _oktaClient = oktaClient;
    }

    [HttpGet]
    public async Task Sso([FromQuery] string name)
    {
        var idpProviders = await _oktaClient.IdentityProviders.ListIdentityProviders(q: name, limit: 1).ToListAsync();

        // search is case insensitive and "starts with", and not "exact match"
        var idp = idpProviders.FirstOrDefault(p => p.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase));

        if (idp == null)
        {
            _logger.LogWarning($"idp name '{name}' doesn't exist");

            var authenticationProperties = new AuthenticationProperties
            {
                RedirectUri = "/"
            };

            await HttpContext.ForbidAsync(authenticationProperties);
        }
        else
        {
            var properties = new AuthenticationProperties
            {
                RedirectUri = "/",
                Items = { ["idp"] = idp.Id }
            };
            await HttpContext.ChallengeAsync(properties);
        }
    }
}

当您点击 /sso?name=MySamlIdp url 时,您将查找在步骤 1 中定义的 MySamlIdp 身份提供程序,并使用其 ID 告诉 Okta 将用户重定向到哪里进行身份验证。在 Okta.Sdk nuget.

中定义的 IOktaClient