在 ASP.NET 服务上验证 ADFS SAML 令牌
在 ASP.NET 服务上验证 ADFS SAML 令牌
引言
我在一家小公司担任开发人员。几个月前,我被要求使用 ADF2.0 和 SAML 令牌来验证桌面应用程序<->服务之间的通信。当时我对这项技术了解不多,开始查找一些细节。
我发现 SAML 令牌并不是身份验证的最佳方案,而且它们的支持确实不好。所以我试图说服他们使用 JWT 令牌。使用它们的主要原因是因为它们在现有产品中有更好的支持,而且重量更轻,因为它们是 JSON 对象,而 SAML 是 XML。但这行不通,因为我的公司已经有一个使用 SAML 令牌的IdentityProvider
。
虽然我的公司里有一些关于如何编写和配置服务来与浏览器使用 ADFS 和 SAML 令牌进行通信的例子,但我一直找不到关于如何使用 C# 编写的桌面应用程序来完成它的完整例子。
背景
我的主要问题是
- 如何在 C# 的桌面应用程序上获取令牌?
- 每次如何发送令牌?
- 如何在服务上验证令牌?
在搜索过程中,前两个问题实际上并不难在互联网上找到答案。
第一个问题引导我找到了这些网站,以及更多 (我在我的 GMAIL 历史记录中找到的那些)
- http://claimsid.codeplex.com/discussions/461755
- http://msdn.microsoft.com/en-us/magazine/ee335705.aspx
- http://leastprivilege.com/2012/03/14/asp-net-webapi-security-4-examples-for-various-authentication-scenarios/
有很多关于如何使用 C# 为您的域 STS 请求身份令牌,如何将令牌发送到另一个域的 STS 并请求一个将在另一个域中工作的令牌的信息。
对于如何将令牌发送到服务的问题,我通常找到的答案是
- 将它放在授权标头中,如下所示:"SAML " + tokenXML
我正是这样做的,但是我的已配置 ASP.NET 服务没有响应我的请求。我以为我做错了什么,但是当我使用 Chrome 访问同一服务时,它就可以工作了,我可以在 Chrome 的开发者工具中看到令牌,并且可以看到与两个域的 STS 的通信,并且服务也回复了。它之所以有效,是因为已配置的服务使用了重定向,而 Chrome خود 就完成了所有魔术。
所以我到处寻找答案,但找不到任何答案。
所以最后我开始阅读关于服务端的知识。我备份了我的 web.config 文件,运行 WIF 工具来配置我的服务以使用证书和令牌进行安全通信,然后将新的 web.config 与旧的进行比较。在看到添加了什么内容之后,我开始查找所有模块及其职责,禁用它们,并观察好的 Chrome 在每次删除模型后的反应。
几个小时后,我找到了我正在寻找的东西,WSFederationAuthenticationModule
是使所有魔术发生的模块。所以我决定使用调试工具来查看它的作用。我扩展了该模块,并在服务中使用我的新模块。在扩展该模块时,我覆盖了我可以覆盖的每个方法,并在其中每个方法中放置了一个断点。在这样做时,我开始越来越确信我走在正确的道路上。
所以经过几个小时的调试后,我进行了适当的更改,并让我的模块查找我通过授权标头发送的令牌。在调试过程中,我注意到在与 Chrome 通信时,令牌不在标头中,而是随服务本身在第一次正确身份验证后提供给应用程序的 cookie 一起出现。所以在进行了一些更改之后,我能够使该服务与我的应用程序进行身份验证,并保持与 Chrome 相同的行为。
Using the Code
所以基本上,您只需要从这里创建模块,在 web.config 中将 WSFederationAuthenticationModule
替换为您的模块,它就会像一个魅力一样工作。
public class MyWSFederationAuthenticationModule : WSFederationAuthenticationModule
{
#region Consts
private const string SAML_SCHEME = "SAML";
private const string AUTHORIZATION_HEADER = "Authorization";
#endregion
#region Public Methods
public override SignInResponseMessage GetSignInResponseMessage(HttpRequest request)
{
if (IsAuthorizationHeaderContainsSamlToken(request))
{
string authorizationHeader = GetAuthorizationHeader(request);
return new SignInResponseMessage(request.Url, authorizationHeader);
}
}
public override string GetXmlTokenFromMessage(SignInResponseMessage message,
WSFederationSerializer federationSerializer)
{
if (message.Result.StartsWith(SAML_SCHEME))
{
// NOTE: +1 for the space after the 'SAML' string
int tokenStartIndex = SAML_SCHEME.Length + 1;
return message.Result.Substring(tokenStartIndex);
}
return base.GetXmlTokenFromMessage(message, federationSerializer);
}
public override bool CanReadSignInResponse(HttpRequest request, bool onPage)
{
if (IsAuthorizationHeaderContainsSamlToken(request))
{
return true;
}
return base.CanReadSignInResponse(request, onPage);
}
#endregion
#region Protected Methods
protected override string GetReturnUrlFromResponse(HttpRequest request)
{
if (IsAuthorizationHeaderContainsSamltoken(request))
{
return request.RawUrl;
}
return base.GetReturnUrlFromResponse(request);
}
#endregion
#region Private Methods
private bool HasAuthorizationHeader(HttpRequest request)
{
return request.Headers.AllKeys.Contains(AUTHORIZATION_HEADER);
}
private string GetAuthorizationHeader(HttpRequest request)
{
return request.Headers[AUTHORIZATION_HEADER];
}
private bool IsAuthorizationHeadercontainsSamlToken(HttpRequest request)
{
if (!HasAuthorizationHeader(request))
{
return false;
}
string authorizationHeader = GetAuthorizationHeader(request);
if (string.IsNullOrEmpty(authorizationHeader))
{
return false;
}
return authorizationHeader.StartsWith(string.Format("{0} ", SAML_SCHEME)) &&
(authorizationHeader.Length > SAML_SCHEME.Length);
}
#endregion
}
我认为代码是不言自明的,所以几乎没有注释。如果您不理解任何内容,欢迎提问。
希望我至少帮助了少数人。 =]