将SAML令牌传递给web api调用

时间:2014-02-24 22:13:28

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

我有一个通过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服务?

3 个答案:

答案 0 :(得分:1)

我一直在应对同样的挑战。问题的核心是SAML是一个基于SOAP的规范;它适用于跨多个SOAP处理节点的消息保护,无论传输如何。

另一方面,REST就是运输。因此,REST端点的安全性(没有双关语意图)依赖于传输安全性,例如SSL。

我们实施了一个“hack”,我们使用具有固定用户名的NetworkCredential,如“saml_user”,并将密码设置为SAML令牌。但这是一个短期的事情。你真的想在每次通话时增加8到10 KB吗?

简单的Web令牌更适合REST安全性。实际上,Microsoft的基于声明的身份和访问控制指南(第2版)有一整章专门用于从SAML令牌提供程序桥接到简单Web令牌。请参阅Accessing REST Services from a Windows Phone Device

答案 1 :(得分:1)

(SET)

            string samlString = "blah blah blah";

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

            string base64SamlString = Convert.ToBase64String(bytes);

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

(GET)

        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()));

        }

答案 2 :(得分:0)

当您要访问通过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;
}