如何从 OKTA 获得用于 unit/integration 测试的 SAML 响应
How to get a SAML Response from OKTA for unit/integration testing
我正在从事他们通过 OKTA 的 SAML 进行身份验证的项目。我已经成功地将 SAMLResponse(通过 POST 方法)发送到网站的集成工作。
以真正的 TDD 方式,我开始编写一些单元测试。我的单元测试采用 SAMLResponse(Base64 编码)。然而,我所有的单元测试都有效,因为 SAMLResponse 只有几分钟的生命周期(过期),我的单元测试在几分钟后中断。
所以我需要定期登录 OKTA,然后使用 Chrome 开发工具捕获发送到我的开发站点的流量。然后,我将 SAMLResponse 复制并粘贴到我的单元测试中,然后返回通过单元测试。显然,这不是理想的情况。
所以我的问题是如何以自动方式(最好是在 C# 中)登录 Okta 以获得 SAMLResponse?我假设有一些 URL 我可以 POST 使用用户名和密码并取回 SAMLReponse。我所有的 Fiddler 尝试理解所需的通信都让我感到沮丧。我正在寻找您可能拥有的任何指导。提前致谢。
有多种方法可以进行您建议的那种测试,以下是立即想到的方法:
- 使用 RestSharp 或类似的库在 C# 中编写一个简单的 HTTP "User Agent"。
- 针对外部模拟 IdP 构建集成测试。
- 使用您自己的密钥修改
SAMLResponse
和 re-sign。
我在下面详细介绍了每种方法。
写一个简单的 HTTP "User Agent"
我建议这种方法,我建议采用以下两种方法之一:
- 编写一个通用的用户代理来检测并填写用户名和密码表单字段。我在 Python 中写了一个名为“saml-messenger" that takes this approach, the core code is in the file named messenger.py.
的工具
- 使用 Okta 的 API 获取会话令牌并使用它来获取
SAMLResponse
。 okta-aws-cli-assume-role project takes this approach. The code for fetching the session token and exchanging that session token for a SAMLResponse 都可以在 src/main/java/com/okta/tools/awscli.java
文件中找到。
第一种方法更通用,应该适用于任何具有用户名和密码字段的 IdP。第二种方法可能是您正在寻找的方法,但特定于 Okta。
无论哪种情况,我都建议在 Okta 中创建一个特殊用户,该用户被锁定并仅用于测试。
针对模拟 IdP 构建集成测试
这种方法会让你设置一个模拟 IdP,它会在没有身份验证的情况下给你一个 SAMLResponse
,我以前使用 saml-idp 项目来做到这一点。
这种方法的好处是,它应该需要更少的 C# 来编写,但代价是承担另一个依赖项。
修改 re-signing SAMLResponse
我真的只是为了完整起见才在此处包含此选项,并警告您不要尝试这种方法。了解 SAML 会让您直面疯狂和后悔。我不建议使用这种方法。
我想出了一个可行的解决方案并想与社区分享。 我不确定根据其他作者 (Joël Franusic) 的有用反馈回答我自己的问题的协议。如果我违反协议,请告诉我。
感谢 Joël Franusic 的指点。我实施了他的 1.2 解决方案(带有 Okta 客户端的用户代理)在他的参考资料和 Okta 网站上的其他一些文档之间,我最终能够拼凑出工作代码。
private static async Task<string> GetTestSamlResponse()
{
try
{
// settings specific to my Okta instance
string username = "USERNAME GOES HERE";
string password = "PASSWORD GOES HERE";
var apiToken = "API TOKEN GOES HERE";
// this is the unique domain issued to your account.
// If you setup a dev account you'll have a domain in the form https://dev-<AccountNumber>.oktapreview.com.
// account number is a unique number issues by Okta when you sign up for the account
var baseUrl = "YOUR BASE URL GOES HERE";
// In Okta Admin UI, click "Applications" in main menu, choose your app, click "Sign On" tab. Under Sign On Methods, then under SAML 2.0, click "View Setup Instructions"
// Get the url called "Identity Provider Single Sign-On URL", paste it in th below line
var ssoUrl = "YOUR SSO URL GOES HERE";
// construct an Okta settings object
var settings = new Okta.Core.OktaSettings
{
ApiToken = apiToken,
BaseUri = new Uri(baseUrl)
};
// get session token from Okta
var authClient = new Okta.Core.Clients.AuthClient(settings);
var authResponse = authClient.Authenticate(username, password);
var sessionToken = authResponse.SessionToken;
// start session and get a cookie token
var sessionsClient = new Okta.Core.Clients.SessionsClient(settings);
var session = sessionsClient.CreateSession(sessionToken);
var cookieToken = session.CookieToken;
// using the cookie token, get the SAMLResponse from Okta via a HTTP GET.
var httpClient = new System.Net.Http.HttpClient();
// add User-Agent header, because apparently Okta is expecting this information.
// If you don't pass something, the Okta site will return a 500 - Internal Server error
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "UnitTest");
// add the cookie token to the URL query string
string url = string.Format("{0}?onetimetoken={1}", ssoUrl, cookieToken);
// do the HTTP GET
using (var response = await httpClient.GetAsync(url))
{
if (response.StatusCode == HttpStatusCode.OK)
{
// read the HTML returned
string html = await response.Content.ReadAsStringAsync();
// parse the HTML to get the SAMLResponse (using HtmlAgilityPack from NuGet)
HtmlAgilityPack.HtmlDocument htmlDoc = new HtmlAgilityPack.HtmlDocument();
htmlDoc.LoadHtml(html);
// from the input field called SAMLResponse, get the "value" attribute
string samlResponse = htmlDoc.DocumentNode.SelectSingleNode("//input[@name='SAMLResponse']").Attributes["value"].Value;
return samlResponse;
}
else
throw new Exception(string.Format("Error getting SAML Response {0}", response.StatusCode));
}
}
catch (Exception ex)
{
throw;
}
}
我正在从事他们通过 OKTA 的 SAML 进行身份验证的项目。我已经成功地将 SAMLResponse(通过 POST 方法)发送到网站的集成工作。
以真正的 TDD 方式,我开始编写一些单元测试。我的单元测试采用 SAMLResponse(Base64 编码)。然而,我所有的单元测试都有效,因为 SAMLResponse 只有几分钟的生命周期(过期),我的单元测试在几分钟后中断。
所以我需要定期登录 OKTA,然后使用 Chrome 开发工具捕获发送到我的开发站点的流量。然后,我将 SAMLResponse 复制并粘贴到我的单元测试中,然后返回通过单元测试。显然,这不是理想的情况。
所以我的问题是如何以自动方式(最好是在 C# 中)登录 Okta 以获得 SAMLResponse?我假设有一些 URL 我可以 POST 使用用户名和密码并取回 SAMLReponse。我所有的 Fiddler 尝试理解所需的通信都让我感到沮丧。我正在寻找您可能拥有的任何指导。提前致谢。
有多种方法可以进行您建议的那种测试,以下是立即想到的方法:
- 使用 RestSharp 或类似的库在 C# 中编写一个简单的 HTTP "User Agent"。
- 针对外部模拟 IdP 构建集成测试。
- 使用您自己的密钥修改
SAMLResponse
和 re-sign。
我在下面详细介绍了每种方法。
写一个简单的 HTTP "User Agent"
我建议这种方法,我建议采用以下两种方法之一:
- 编写一个通用的用户代理来检测并填写用户名和密码表单字段。我在 Python 中写了一个名为“saml-messenger" that takes this approach, the core code is in the file named messenger.py. 的工具
- 使用 Okta 的 API 获取会话令牌并使用它来获取
SAMLResponse
。 okta-aws-cli-assume-role project takes this approach. The code for fetching the session token and exchanging that session token for a SAMLResponse 都可以在src/main/java/com/okta/tools/awscli.java
文件中找到。
第一种方法更通用,应该适用于任何具有用户名和密码字段的 IdP。第二种方法可能是您正在寻找的方法,但特定于 Okta。
无论哪种情况,我都建议在 Okta 中创建一个特殊用户,该用户被锁定并仅用于测试。
针对模拟 IdP 构建集成测试
这种方法会让你设置一个模拟 IdP,它会在没有身份验证的情况下给你一个 SAMLResponse
,我以前使用 saml-idp 项目来做到这一点。
这种方法的好处是,它应该需要更少的 C# 来编写,但代价是承担另一个依赖项。
修改 re-signing SAMLResponse
我真的只是为了完整起见才在此处包含此选项,并警告您不要尝试这种方法。了解 SAML 会让您直面疯狂和后悔。我不建议使用这种方法。
我想出了一个可行的解决方案并想与社区分享。 我不确定根据其他作者 (Joël Franusic) 的有用反馈回答我自己的问题的协议。如果我违反协议,请告诉我。
感谢 Joël Franusic 的指点。我实施了他的 1.2 解决方案(带有 Okta 客户端的用户代理)在他的参考资料和 Okta 网站上的其他一些文档之间,我最终能够拼凑出工作代码。
private static async Task<string> GetTestSamlResponse()
{
try
{
// settings specific to my Okta instance
string username = "USERNAME GOES HERE";
string password = "PASSWORD GOES HERE";
var apiToken = "API TOKEN GOES HERE";
// this is the unique domain issued to your account.
// If you setup a dev account you'll have a domain in the form https://dev-<AccountNumber>.oktapreview.com.
// account number is a unique number issues by Okta when you sign up for the account
var baseUrl = "YOUR BASE URL GOES HERE";
// In Okta Admin UI, click "Applications" in main menu, choose your app, click "Sign On" tab. Under Sign On Methods, then under SAML 2.0, click "View Setup Instructions"
// Get the url called "Identity Provider Single Sign-On URL", paste it in th below line
var ssoUrl = "YOUR SSO URL GOES HERE";
// construct an Okta settings object
var settings = new Okta.Core.OktaSettings
{
ApiToken = apiToken,
BaseUri = new Uri(baseUrl)
};
// get session token from Okta
var authClient = new Okta.Core.Clients.AuthClient(settings);
var authResponse = authClient.Authenticate(username, password);
var sessionToken = authResponse.SessionToken;
// start session and get a cookie token
var sessionsClient = new Okta.Core.Clients.SessionsClient(settings);
var session = sessionsClient.CreateSession(sessionToken);
var cookieToken = session.CookieToken;
// using the cookie token, get the SAMLResponse from Okta via a HTTP GET.
var httpClient = new System.Net.Http.HttpClient();
// add User-Agent header, because apparently Okta is expecting this information.
// If you don't pass something, the Okta site will return a 500 - Internal Server error
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "UnitTest");
// add the cookie token to the URL query string
string url = string.Format("{0}?onetimetoken={1}", ssoUrl, cookieToken);
// do the HTTP GET
using (var response = await httpClient.GetAsync(url))
{
if (response.StatusCode == HttpStatusCode.OK)
{
// read the HTML returned
string html = await response.Content.ReadAsStringAsync();
// parse the HTML to get the SAMLResponse (using HtmlAgilityPack from NuGet)
HtmlAgilityPack.HtmlDocument htmlDoc = new HtmlAgilityPack.HtmlDocument();
htmlDoc.LoadHtml(html);
// from the input field called SAMLResponse, get the "value" attribute
string samlResponse = htmlDoc.DocumentNode.SelectSingleNode("//input[@name='SAMLResponse']").Attributes["value"].Value;
return samlResponse;
}
else
throw new Exception(string.Format("Error getting SAML Response {0}", response.StatusCode));
}
}
catch (Exception ex)
{
throw;
}
}