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