【问题标题】:Pass SAML token into web api call将 SAML 令牌传递到 Web api 调用
【发布时间】:2014-02-24 22:13:27
【问题描述】:

我有一个通过 ADFS 进行身份验证的 Web 应用程序和 Web api 服务。它们包含在同一个 IIS 应用程序中,Web 应用程序可以毫无问题地回调 Web api 服务。

我现在正尝试从不同的应用程序调用相同的服务,但在传递令牌时遇到了问题。我可以使用以下代码验证和检索 SAML 令牌:

var stsEndpoint = "https://MyAdfsServer/adfs/services/trust/13/UsernameMixed";
var reliantPartyUri = "https://MyDomain/AppRoot/";

var factory = new Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannelFactory(
            new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential),
            new EndpointAddress(stsEndpoint));

factory.TrustVersion = System.ServiceModel.Security.TrustVersion.WSTrust13;

// Username and Password here...
factory.Credentials.UserName.UserName = @"Domain\UserName";
factory.Credentials.UserName.Password = "Password";

var rst = new RequestSecurityToken
    {
        RequestType = RequestTypes.Issue,
        AppliesTo = new EndpointAddress(reliantPartyUri),
        KeyType = KeyTypes.Bearer,
    };

var channel = factory.CreateChannel();
var token = channel.Issue(rst) as GenericXmlSecurityToken;

var saml = token.TokenXml.OuterXml;

但是,我不确定如何将 saml 传递给 web api 调用。我试过这个:

using (var handler = new HttpClientHandler() 
    { 
        ClientCertificateOptions = ClientCertificateOption.Automatic,
        AllowAutoRedirect = false
    })
{
    using (var client = new HttpClient(handler))
    {
        client.BaseAddress = new Uri("https://MyDomain/AppRoot/api/");

        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("SAML", saml);

        HttpResponseMessage response = client.GetAsync("MyService/Get/").Result;

        // Get the results...
        var result = response.Content.ReadAsStringAsync().Result;
        var status = response.StatusCode;
    }
}

这将返回状态代码 302 并尝试将我重定向到 ADFS 服务器进行身份验证。还有其他方法可以将 SAML 令牌传递给 Web api 服务吗?

【问题讨论】:

  • SAML 令牌和 REST API 不能很好地配合使用。考虑使用 OAuth 2.0。
  • 我也有同样的问题,因为我有 Windows Server 2008 R2 并且 Oauth 2.0 不可用,你设法做到了吗?
  • 不,我们最终创建了一个单独的 IIS 应用程序,该应用程序通过 Windows Auth 进行身份验证以公开所需的服务。不是最好的解决方案,但它符合我们的需求。

标签: c# asp.net-web-api saml adfs


【解决方案1】:

(设置)

            string samlString = "blah blah blah";

            byte[] bytes = Encoding.UTF8.GetBytes(samlString);

            string base64SamlString = Convert.ToBase64String(bytes);

            myHttpClient.DefaultRequestHeaders.Add("X-My-Custom-Header", base64SamlString);

(获取)

        IEnumerable<string> headerValues = request.Headers.GetValues("X-My-Custom-Header");

        if (null != headerValues)

        {

            var encoding = Encoding.GetEncoding("iso-8859-1");

            string samlToken = encoding.GetString(Convert.FromBase64String(headerValues.FirstOrDefault()));

        }

【讨论】:

  • 当我调用 encoding.GetString 时,这种方法给了我一个error 411: Length required
  • “headerValues.FirstOrDefault()”的字符串长度是多少?
  • 抱歉,我最终将数据编码为 POST 数据(即不是作为标头)并且丢失了该代码
【解决方案2】:

当您想要访问受 SSO 保护的资源(我假设是 ADFS)时,我发现使用以下方法最容易:显示 WebBrowser 元素,让用户输入凭据,然后获取全局 cookie,然后将它们传递给执行实际 HTTP 操作的新 HttpClient

这是一个完整的代码示例,它从受 SAML 保护的 Jenkins 服务器下载所有构建状态:

private void Start()
{
    var t = new Thread(ThreadProc);
    t.SetApartmentState(ApartmentState.STA);
    t.Start();
    t.Join();
}

public async void ThreadProc()
{
    try
    {
        var urlBase = "https://JENKINS/";
        var url = urlBase + "job/JOBNAME/api/json?depth=1&tree=lastBuild[timestamp],builds[number,result,timestamp,url,actions[lastBuiltRevision[SHA1,branch[name]],totalCount,failCount,skipCount],building,duration]";

        var form = new Form();
        var browser = new System.Windows.Forms.WebBrowser();

        browser.SetBounds(0, 0, 400, 400);
        form.Size = new System.Drawing.Size(400, 400);
        form.Controls.AddRange(new Control[] { browser });
        form.FormBorderStyle = FormBorderStyle.FixedDialog;
        form.StartPosition = FormStartPosition.CenterScreen;
        form.MinimizeBox = false;
        form.MaximizeBox = false;

        // Navigate to base URL. It should internally forward to login form. After logging in, close browser window.
        browser.Navigate(urlBase);
        form.ShowDialog();

        var cookieString = GetGlobalCookies(urlBase);
        var cookieContainer = new System.Net.CookieContainer();
        using (var handler = new HttpClientHandler() { CookieContainer = cookieContainer })
        using (var client = new HttpClient(handler) { BaseAddress = new Uri(urlBase, UriKind.Absolute) })
        {
            cookieContainer.SetCookies(client.BaseAddress, cookieString);
            var response = await client.GetAsync(url);
            if (response.IsSuccessStatusCode)
            {
                var responseStream = await response.Content.ReadAsStreamAsync();
                using (var reader = new System.IO.StreamReader(responseStream))
                {
                    var responseString = await reader.ReadToEndAsync();
                }
            }
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex);
    }
}

[System.Runtime.InteropServices.DllImport("wininet.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto, SetLastError = true)]
private static extern bool InternetGetCookieEx(string pchURL, string pchCookieName,
    System.Text.StringBuilder pchCookieData, ref uint pcchCookieData, int dwFlags, IntPtr lpReserved);

private const int INTERNET_COOKIE_HTTPONLY = 0x00002000;

public string GetGlobalCookies(string uri)
{
    uint uiDataSize = 2048;
    var sbCookieData = new System.Text.StringBuilder((int)uiDataSize);
    if (InternetGetCookieEx(uri, null, sbCookieData, ref uiDataSize,
            INTERNET_COOKIE_HTTPONLY, IntPtr.Zero)
        &&
        sbCookieData.Length > 0)
    {
        return sbCookieData.ToString().Replace(";", ",");
    }
    return null;
}

【讨论】:

  • 实际上为我工作,非常感谢,这是我正在寻找的,因为我想让用户在 SSO 上登录(如果尚未完成)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-04-10
  • 1970-01-01
  • 2018-10-23
  • 1970-01-01
  • 2021-09-02
  • 2021-04-08
  • 2022-08-24
相关资源
最近更新 更多