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 中配置身份提供者,接收断言,并且根据日志,断言已成功验证并自动配置用户。
但是从现在开始会发生什么?
- 在 Idp 发起的流程中,如何将用户重定向到应用程序 url?
RelayState
由 Idp 配置是唯一的选择吗?如果我们更改我们的应用程序 url,我不喜欢我们所有的客户都必须进行更改的想法。我期望 link Idp 到 Okta 应用程序的可能性,因此重定向 url 将由该应用程序配置。
- 重定向到我们的应用程序站点后,如何在 Asp.Net Core 中对用户进行身份验证?我实现了自定义
AuthenticationHandler
,它读取 Okta 在重定向时设置的 sid
cookie,并从 Okta 检索会话信息。从那里我得到用户名并创建 Principal
。这种方法有效,但看起来我错了 - 它太手动了,我希望 Okta.Sdk 为我这样做(如果这是正确的身份验证方法)。
- 断言验证成功后,是否可以将 saml 令牌交换为 OAuth 令牌以在应用程序中进行身份验证?
基于纯 SAML 方法回答您的问题:
- 您可以使用 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.
之间的映射
- 您希望 Okta 成为您的 SAML 身份提供者,而您的应用程序成为 SAML 服务提供者。您尝试了此 cookie-based 解决方法,因为您不了解如何在您的 .NET 应用程序中实施 SAML。
Okta 不为充当 SAML 服务提供商的 .NET 应用程序提供 SAML 工具包或 SDK,他们推荐第 3 方库。 ASP.NET 不支持开箱即用的 SAML……这是 Okta 推荐第 3 方的另一个原因。 (Microsoft 不是这些第 3 方之一)。两个流行的、免费的和广泛使用的选择是 Sustainsys and ITFoxTec。看看他们的文档,选择一个并实施它。在那之后你不应该诉诸你的 cookie 解决方法。
- 在您的应用程序处理 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 回调样式。
您的应用程序将通过 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 流激活第一跳来触发此序列。
在集成的 front-end 回调样式中,您将使用 Microsoft 接口 + Okta 库,如 Okta 的 example.
所示
使用 Okta 作为您的身份存储和经过身份验证的委托人,您可以通过其他步骤从 Okta 获取令牌。如果您的目标是通过 Okta 从第 3 方启用 API 调用您的应用程序,请查看 API callback style of integration.
终于,我能够让它按照我想要的方式工作了。目前我遇到 Idp-initiated 流程的问题,如果我找到解决方案,我会更新答案。
首先配置您组织的 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 签名证书)的信息。
下一步在 Okta 中创建 Web application。你的 Login redirect URIs
应该以 /authorization-code/callback
结束,否则你将不得不在代码中的 CallbackPath
属性 中配置它(见下文)。您不需要实现此端点,框架会为您完成。
现在安装 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_id
和 app_client_secret
。
现在是最有趣的部分。如果你想为内部用户(不是外部 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
我正在用 Okta 替换我们内部发明的身份验证服务,并且需要在我们的应用程序中支持使用 Saml2 的 SSO。 Okta 可以配置为服务提供商,发送 SAMLRequest
并接收和验证 SAMLResponse
。到目前为止一切顺利:我能够在 Okta 中配置身份提供者,接收断言,并且根据日志,断言已成功验证并自动配置用户。
但是从现在开始会发生什么?
- 在 Idp 发起的流程中,如何将用户重定向到应用程序 url?
RelayState
由 Idp 配置是唯一的选择吗?如果我们更改我们的应用程序 url,我不喜欢我们所有的客户都必须进行更改的想法。我期望 link Idp 到 Okta 应用程序的可能性,因此重定向 url 将由该应用程序配置。 - 重定向到我们的应用程序站点后,如何在 Asp.Net Core 中对用户进行身份验证?我实现了自定义
AuthenticationHandler
,它读取 Okta 在重定向时设置的sid
cookie,并从 Okta 检索会话信息。从那里我得到用户名并创建Principal
。这种方法有效,但看起来我错了 - 它太手动了,我希望 Okta.Sdk 为我这样做(如果这是正确的身份验证方法)。 - 断言验证成功后,是否可以将 saml 令牌交换为 OAuth 令牌以在应用程序中进行身份验证?
基于纯 SAML 方法回答您的问题:
- 您可以使用 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.
之间的映射- 您希望 Okta 成为您的 SAML 身份提供者,而您的应用程序成为 SAML 服务提供者。您尝试了此 cookie-based 解决方法,因为您不了解如何在您的 .NET 应用程序中实施 SAML。
Okta 不为充当 SAML 服务提供商的 .NET 应用程序提供 SAML 工具包或 SDK,他们推荐第 3 方库。 ASP.NET 不支持开箱即用的 SAML……这是 Okta 推荐第 3 方的另一个原因。 (Microsoft 不是这些第 3 方之一)。两个流行的、免费的和广泛使用的选择是 Sustainsys and ITFoxTec。看看他们的文档,选择一个并实施它。在那之后你不应该诉诸你的 cookie 解决方法。
- 在您的应用程序处理 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 回调样式。
您的应用程序将通过 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 流激活第一跳来触发此序列。在集成的 front-end 回调样式中,您将使用 Microsoft 接口 + Okta 库,如 Okta 的 example.
所示使用 Okta 作为您的身份存储和经过身份验证的委托人,您可以通过其他步骤从 Okta 获取令牌。如果您的目标是通过 Okta 从第 3 方启用 API 调用您的应用程序,请查看 API callback style of integration.
终于,我能够让它按照我想要的方式工作了。目前我遇到 Idp-initiated 流程的问题,如果我找到解决方案,我会更新答案。
首先配置您组织的 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 签名证书)的信息。下一步在 Okta 中创建 Web application。你的
Login redirect URIs
应该以/authorization-code/callback
结束,否则你将不得不在代码中的CallbackPath
属性 中配置它(见下文)。您不需要实现此端点,框架会为您完成。现在安装 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_id
和 app_client_secret
。
现在是最有趣的部分。如果你想为内部用户(不是外部 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