65.9K
CodeProject 正在变化。 阅读更多。
Home

在 ASP.NET 服务上验证 ADFS SAML 令牌

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2014年11月14日

CPOL

4分钟阅读

viewsIcon

38180

downloadIcon

958

在 ASP.NET 服务上验证 ADFS SAML 令牌

引言

我在一家小公司担任开发人员。几个月前,我被要求使用 ADF2.0 和 SAML 令牌来验证桌面应用程序<->服务之间的通信。当时我对这项技术了解不多,开始查找一些细节。

我发现 SAML 令牌并不是身份验证的最佳方案,而且它们的支持确实不好。所以我试图说服他们使用 JWT 令牌。使用它们的主要原因是因为它们在现有产品中有更好的支持,而且重量更轻,因为它们是 JSON 对象,而 SAML 是 XML。但这行不通,因为我的公司已经有一个使用 SAML 令牌的IdentityProvider

虽然我的公司里有一些关于如何编写和配置服务来与浏览器使用 ADFS 和 SAML 令牌进行通信的例子,但我一直找不到关于如何使用 C# 编写的桌面应用程序来完成它的完整例子。

背景

我的主要问题是

  1. 如何在 C# 的桌面应用程序上获取令牌?
  2. 每次如何发送令牌?
  3. 如何在服务上验证令牌?

在搜索过程中,前两个问题实际上并不难在互联网上找到答案。

第一个问题引导我找到了这些网站,以及更多 (我在我的 GMAIL 历史记录中找到的那些)

有很多关于如何使用 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
}

我认为代码是不言自明的,所以几乎没有注释。如果您不理解任何内容,欢迎提问。

希望我至少帮助了少数人。 =]

© . All rights reserved.