SharePoint 2013 中的联合身份验证:获取 rtFa 和 FedAuth Cookie。

11

场景如下:我需要对用户(使用他的大学帐户)在他的大学Sharepoint网站上执行联合身份验证,以获取FedAuth和rtFa cookie(我必须将其传递给SharePoint REST Web服务,以便访问资源)。

我尝试过一些方法,但每种方法都存在至少一个问题:

1)使用Microsoft.SharePoint.Client库

ClientContext context = new ClientContext(host);
SharePointOnlineCredentials creds = new SharePointOnlineCredentials(user, passw);
context.Credentials = creds;

Uri sharepointuri = new Uri(host);
string authCookie = creds.GetAuthenticationCookie(sharepointuri);

Web web = context.Web;
context.Load(web, w=>w.Lists);
context.ExecuteQuery();

fedAuthString = authCookie.Replace("SPOIDCRL=", string.Empty);

我成功获取了FedAuth cookie,但是无法获取rtFa cookie.

此时如何获取rtFa cookie? 我能拦截涉及此操作的HTTP请求(即context.ExecuteQuery()),该请求是否包含标头中的rtFa cookie? 或者,我是否仅依靠FedAuth cookie就能获取rtFa cookie?

2)使用MsOnlineClaimsHelper

这是一个可以在互联网上找到的帮助类(例如,在此处http://blog.kloud.com.au/tag/msonlineclaimshelper/)。这个类,在正常身份验证情况下工作,但是在联合身份验证下失败了

因此,我进行了调整,以使其在这种情况下工作。 只要我理解,步骤如下:

  1. 使用用户名和密码对大学的STS ADFS服务(“联盟方”或ISSUER)进行身份验证 - 这里Relying Party是Sharepoint O365 STS(“https://login.microsoftonline.com/extSTS.srf”)
  2. 如果验证成功,则会收到包含声明和安全令牌的SAML断言
  3. 现在,我通过传递安全令牌来对SharePoint站点进行身份验证
  4. 如果令牌得到识别,则会收到一个响应,其中包含两个Cookie(FedAuth和rtFa)

我不是这方面的专家,我写了以下代码:

这是调用上述方法并尝试从两步骤中的凭据获取FedAuth和rtFa的代码(步骤1:从联盟方获取SAML令牌;步骤2:将来自联盟方的令牌传递给Sharepoint):

     private List<string> GetCookies(){
            // 1: GET SAML XML FROM FEDERATED PARTY THE USER BELONGS TO
            string samlToken = getResponse_Federation(sts: "https://sts.FEDERATEDDOMAIN.com/adfs/services/trust/13/usernamemixed/",
                realm: "https://login.microsoftonline.com/extSTS.srf");

            // 2: PARSE THE SAML ASSERTION INTO A TOKEN 
            var handlers = FederatedAuthentication.ServiceConfiguration.SecurityTokenHandlers;
            SecurityToken token = handlers.ReadToken(new XmlTextReader(new StringReader(samlToken )));

            // 3: REQUEST A NEW TOKEN BASED ON THE ISSUED TOKEN
            GenericXmlSecurityToken secToken = GetO365BinaryTokenFromToken(token);

            // 4: NOW, EASY: I PARSE THE TOKEN AND EXTRACT FEDAUTH and RTFA
            ...............
    }


    private string getResponse_Federation(string stsUrl, string relyingPartyAddress)
    {
        var binding = new Microsoft.IdentityModel.Protocols.WSTrust.Bindings.UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential);
        binding.ClientCredentialType = HttpClientCredentialType.None;

        var factory = new WSTrustChannelFactory(binding,  stsUrl);

        factory.Credentials.UserName.UserName = "username";
        factory.Credentials.UserName.Password = "password";
        factory.Credentials.SupportInteractive = false;
        factory.TrustVersion = TrustVersion.WSTrust13;

        IWSTrustChannelContract channel = null;
        try
        {
            var rst = new RequestSecurityToken
            {
                RequestType = WSTrust13Constants.RequestTypes.Issue,
                AppliesTo = new EndpointAddress(relyingPartyAddress), //("urn:sharepoint:MYFEDERATEDPARTY"),
                ReplyTo = relyingPartyAddress,
                KeyType = WSTrust13Constants.KeyTypes.Bearer,
                TokenType =  "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0",
                RequestDisplayToken = true,
            };
            channel = (WSTrustChannel)factory.CreateChannel();

            RequestSecurityTokenResponse response = null;
            SecurityToken st = channel.Issue(rst, out response);
            var genericToken = st as GenericXmlSecurityToken;
            return genericToken.TokenXml.OuterXml;
        }
        catch (Exception e)
        {
            return null;
        }
    }

    private GenericXmlSecurityToken GetO365BinaryTokenFromToken(SecurityToken issuedToken)
    {
        Uri u = new Uri("https://login.microsoftonline.com/extSTS.srf");

        WSHttpBinding binding = new WSHttpBinding(SecurityMode.TransportWithMessageCredential);
        binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
        binding.Security.Mode = SecurityMode.TransportWithMessageCredential;
        binding.Security.Message.ClientCredentialType = MessageCredentialType.IssuedToken;

        Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannelFactory channel =
        new Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannelFactory(
            binding, new EndpointAddress("https://login.microsoftonline.com/extSTS.srf"));

        channel.TrustVersion = TrustVersion.WSTrust13;
        channel.Credentials.SupportInteractive = false;

        GenericXmlSecurityToken token = null;

        try
        {
            RequestSecurityToken rst = new RequestSecurityToken(WSTrust13Constants.RequestTypes.Issue, WSTrust13Constants.KeyTypes.Bearer)
            {
            };
            rst.AppliesTo = new EndpointAddress("urn:sharepoint:MYFEDERATEDPARTY");
            channel.ConfigureChannelFactory();
            var chan = (Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannel)channel.CreateChannelWithIssuedToken(issuedToken);

            RequestSecurityTokenResponse rstr = null;

            token = chan.Issue(rst, out rstr) as GenericXmlSecurityToken;

            return token;
        }
        catch (Exception ex){
            Trace.TraceWarning("WebException in getO365BinaryTokenFromADFS: " + ex.ToString());
            throw;
        }
    }

我能够从大学的STS获取回一个SAML令牌。但是,在解析时,生成的SecurityToken没有安全密钥(即SecurityKeys集合为空)。

由于没有密钥,我可以获取GetO365BinaryTokenFromToken(),但是当我尝试将令牌发送到SharePoint身份验证服务时,我会收到以下错误: "签名令牌Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityToken没有密钥。 该安全令牌用于需要执行加密操作的上下文中,但令牌不包含加密密钥。 要么令牌类型不支持加密操作,要么特定的令牌实例不包含加密密钥。 请检查您的配置,以确保在需要执行加密操作的上下文中未指定已禁用加密的令牌类型(例如UserNameSecurityToken)(例如支持背书的令牌)。

我认为在双方(大学STS ADFS和Sharepoint STS)都存在一些我无法直接控制的配置问题。

我希望更有经验的人能够在这个过程中提供更清晰的建议,并甚至提供建议以使此场景实际可行。

文件下载函数

使用以下函数,我能够通过发出 BOTH FedAuth和rtFa cookie(给定URL,如https://myfederatedparty.sharepoint.com/sites/MYSITE/path/myfile.pdf)来下载文件。如果我不传递rtFa cookie,我会收到一个"未授权"的响应。

    public static async Task<byte[]> TryRawWsCall(String url, string fedauth, string rtfa, CancellationToken ct, TimeSpan? timeout = null) {
        try {
            HttpClientHandler handler = new HttpClientHandler();
            handler.CookieContainer = new System.Net.CookieContainer();
            CookieCollection cc = new CookieCollection();
            cc.Add(new Cookie("FedAuth", fedauth));
            cc.Add(new Cookie("rtFa", rtfa));
            handler.CookieContainer.Add(new Uri(url), cc);

            HttpClient _client = new HttpClient(handler);
            if (timeout.HasValue)
                _client.Timeout = timeout.Value;
            ct.ThrowIfCancellationRequested();

            var resp = await _client.GetAsync(url);
            var result = await resp.Content.ReadAsByteArrayAsync();
            if (!resp.IsSuccessStatusCode)
                return null;
            return result;
        }
        catch (Exception) { return null; }
    }
4个回答

11
实际上,在SharePoint Online/Office 365身份验证方面,只有FedAuth cookie是必需的。
根据 使用基于声明的身份验证在SharePoint Online中进行远程身份验证

FedAuth cookies使联合授权成为可能,rtFA cookie使用户从所有SharePoint站点注销,即使注销过程始于非SharePoint站点。

因此,只需提供SPOIDCRL HTTP头即可在SharePoint Online/Office 365中执行身份验证,例如:
var request = (HttpWebRequest)WebRequest.Create(endpointUri);
var credentials = new SharePointOnlineCredentials(userName,securePassword);
var authCookie = credentials.GetAuthenticationCookie(webUri);
request.Headers.Add(HttpRequestHeader.Cookie, authCookie);

以下示例演示了如何通过提供FedAuth cookie在SharePointOnline/Office 365中执行活动身份验证。

示例1:通过SharePoint 2013 REST API(使用MsOnlineClaimsHelper类)检索FormDigest

public static string GetFormDigest(Uri webUri, string userName, string password)
{
   var claimsHelper = new MsOnlineClaimsHelper(webUri, userName, password);
   var endpointUri = new Uri(webUri,"/_api/contextinfo");
   var request = (HttpWebRequest)WebRequest.Create(endpointUri);
   request.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f");
   request.Method = WebRequestMethods.Http.Post;
   request.Accept = "application/json;odata=verbose";
   request.ContentType = "application/json;odata=verbose";
   request.ContentLength = 0;

   var fedAuthCookie = claimsHelper.CookieContainer.GetCookieHeader(webUri); //FedAuth are getting here
   request.Headers.Add(HttpRequestHeader.Cookie, fedAuthCookie); //only FedAuth cookie are provided here
   //request.CookieContainer = claimsHelper.CookieContainer;
   using (var response = (HttpWebResponse) request.GetResponse())
   {
        using (var streamReader = new StreamReader(response.GetResponseStream()))
        {
                var content = streamReader.ReadToEnd();
                var t = JToken.Parse(content);
                return t["d"]["GetContextWebInformation"]["FormDigestValue"].ToString();
        }     
    }
}

示例2:通过SharePoint 2013 REST API(使用SharePointOnlineCredentials类)检索FormDigest

public static string GetFormDigest(Uri webUri, string userName, string password)
{
   var endpointUri = new Uri(webUri, "/_api/contextinfo");
   var request = (HttpWebRequest)WebRequest.Create(endpointUri);
   request.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f");
   request.Method = WebRequestMethods.Http.Post;
   request.Accept = "application/json;odata=verbose";
   request.ContentType = "application/json;odata=verbose";
   request.ContentLength = 0;

   var securePassword = new SecureString();
   foreach (char c in password)
   {
       securePassword.AppendChar(c);
   }
   request.Credentials = new SharePointOnlineCredentials(userName,securePassword);

   using (var response = (HttpWebResponse)request.GetResponse())
   {
       using (var streamReader = new StreamReader(response.GetResponseStream()))
       {
           var content = streamReader.ReadToEnd();
           var t = JToken.Parse(content);
           return t["d"]["GetContextWebInformation"]["FormDigestValue"].ToString();
        }
   }
}

更新

下载文件示例的修改版本:

public static async Task<byte[]> DownloadFile(Uri webUri,string userName,string password, string relativeFileUrl, CancellationToken ct, TimeSpan? timeout = null)
{
        try
        {

            var securePassword = new SecureString();
            foreach (var c in password)
            {
                securePassword.AppendChar(c);
            }
            var credentials = new SharePointOnlineCredentials(userName, securePassword);
            var authCookie = credentials.GetAuthenticationCookie(webUri);
            var fedAuthString = authCookie.TrimStart("SPOIDCRL=".ToCharArray());
            var cookieContainer = new CookieContainer();
            cookieContainer.Add(webUri, new Cookie("SPOIDCRL", fedAuthString));


            HttpClientHandler handler = new HttpClientHandler();
            handler.CookieContainer = cookieContainer;

            HttpClient _client = new HttpClient(handler);
            if (timeout.HasValue)
                _client.Timeout = timeout.Value;
            ct.ThrowIfCancellationRequested();

            var fileUrl = new Uri(webUri, relativeFileUrl);
            var resp = await _client.GetAsync(fileUrl);
            var result = await resp.Content.ReadAsByteArrayAsync();
            if (!resp.IsSuccessStatusCode)
                return null;
            return result;
        }
        catch (Exception) { return null; }
 }

1
我的问题是,当我尝试通过HttpClient执行HTTP请求从SharePoint下载文件(例如通过URL link )时,仅当我提供FedAuth和rtFa cookie时,响应才成功。仅提供FedAuth是不够的。 因此,我必须获取rtFa cookie或使该请求在没有它的情况下工作。附注:我不知道FormDigest是什么。 - metaphori
1
谢谢,它有效!因此似乎SPOIDCRL和FedAuth cookie之间存在差异。什么是更简单的方法?实际上,我使用这种方法是因为我无法在我的Windows Store应用程序中使用SharePoint客户端库(以及其他库)。 - metaphori
1
太好了!还有另一种选项,如此展示:https://dev59.com/eH_aa4cB1Zd3GeqP67vk#23687279 但是在该示例中它也使用了SharePoint客户端库。 - Vadim Gremyachev
@iAmMortos,确实,在提供的示例中存在对CSOM库的依赖,因为SharePointOnlineCredentials类用于身份验证目的。但是,您也可以考虑访问令牌方法来避免这些依赖关系。 - Vadim Gremyachev
@VadimGremyachev 当使用ADFS SSO时,此方法无法正常工作 - 未处理的异常:System.NullReferenceException:Cookie为空。这是预期的吗?在这种情况下,您如何使用它? - Nicholas DiPiazza
显示剩余6条评论

1

这是我所做的。对于未来的读者可能会有用!

对于我的用例,我正在运行一个WinForms Windows客户端应用程序。我能够使用嵌入式WebBrowser控件获取FedAuthrtFA cookie。

我已经将我的示例测试项目上传到github上,链接在此处:https://github.com/OceanAirdrop/SharePointOnlineGetFedAuthAndRtfaCookie

以下是我所做的:

步骤01:在WebBrowser控件中导航到SharePoint Url

使用WebBrowser控件,首先导航到您可以访问的SharePoint站点上的网页。它可以是任何页面。目的是从加载的页面中获取cookie。此步骤只需要在应用程序中执行一次。

webBrowser1.Navigate(@"https://xx.sharepoint.com/sites/xx/Forms/AllItems.aspx");

步骤02:从WebBrowser控件中获取Cookies

接下来,重写WebBrowser控件中的Navigated事件。这样可以让您知道页面已经完全加载。

现在,这里有个问题!FedAuth cookies是使用HTTPOnly标志编写的,这意味着它们无法从.NET Framework中访问。这意味着如果您尝试访问WebBrowser控件的cookies,您将得到空字符串!

// This line of code wont work and will return null
var cookies = webBrowser1.Document.Cookie;

因此,为了解决这个问题,您需要在 WININET.dll 中调用 InternetGetCookieEx。我从 这里 获取了代码。以下是 Navigated 函数处理程序的样子:

private void webBrowser1_Navigated(object sender, WebBrowserNavigatedEventArgs e)
{
    try
    {
        if (webBrowser1.Url.AbsoluteUri == "about:blank")
            return;

        // This line calls through to InternetGetCookieEx
        var cookieData = GetWebBrowserCookie.GetCookieInternal(webBrowser1.Url, false);

        if (string.IsNullOrEmpty(cookieData) == false)
        {
            textBoxCookie.Text = cookieData;

            var dict = ParseCookieData(cookieData);
            textBoxFedAuth.Text = dict["FedAuth"];
            textBoxrtFa.Text = dict["rtFa"];
        }
    }
    catch (Exception)
    {
    }
}

步骤 03:使用 Cookie 编程方式进行 WebRequest

现在我们已经拥有 FedAuthrtFA cookies,我们可以继续使用 HttpClient 调用任何需要的端点。在我的情况下,调用包含图像的多个端点。代码如下:

private void buttonDownloadImage_Click(object sender, EventArgs e)
{
    try
    {
        var url = $"https://xx.sharepoint.com/sites/xx/xx/Images/{textBoxImageName.Text}";

        var handler = new HttpClientHandler();

        handler.CookieContainer = new System.Net.CookieContainer();

        // Add our cookies to collection
        var cc = new CookieCollection();
        cc.Add(new Cookie("FedAuth", textBoxFedAuth.Text));
        cc.Add(new Cookie("rtFa", textBoxrtFa.Text));

        handler.CookieContainer.Add(new Uri(url), cc);

        var httpClient = new HttpClient(handler);
        var resp = httpClient.GetAsync(url).Result;
        var byteData = resp.Content.ReadAsByteArrayAsync().Result;
               
        if (resp.IsSuccessStatusCode)
        {
            pictureBox1.Image = byteArrayToImage(byteData);
        }
    }
    catch (Exception) 
    { 
            
    }
}

就是这样。而且它的运行非常顺畅。


0

我基于https://stackoverflow.com/users/1375553/vadim-gremyachev的答案创建了一个Github项目,该项目可以生成这些cookie。https://github.com/nddipiazza/SharepointOnlineCookieFetcher

它适用于Windows、Centos7和Ubuntu16,并使用mono develop进行构建,以便它是平台无关的。

旨在面向不使用C#中的CSOM为程序开发者,但仍希望能够轻松获取cookie的用户。

使用方法

仅限一次性步骤:(请参见Access to the path "/etc/mono/registry" is denied

sudo mkdir /etc/mono
sudo mkdir /etc/mono/registry
sudo chmod uog+rw /etc/mono/registry

运行程序:

Linux:./SharepointOnlineSecurityUtil -u youruser@yourdomain.com -w https://tenant.sharepoint.com

Windows:SharepointOnlineSecurityUtil.exe -u youruser@yourdomain.com -w https://tenant.sharepoint.com

提示输入密码

标准输出结果将包含SPOIDCRL cookie。


0

为了我的目的,我仍需要FedAuth和rtFa两个cookie。我尝试只使用FedAuth,但是没有两者都存在就无法正常工作。另一位开发人员证实了他也遇到了同样的问题。

注意:必须在您的租户中启用传统认证才能使其正常工作。

这里有一个线程,可以帮助获取FedAuth和rtFa。

  1. https://login.microsoftonline.com/extSTS.srf发送Post请求,并使用以下正文。
    将UserName、Password、EndPoint Address替换为相关值。
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
      xmlns:a="http://www.w3.org/2005/08/addressing"
      xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
  <s:Header>
    <a:Action s:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</a:Action>
    <a:ReplyTo>
      <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
    </a:ReplyTo>
    <a:To s:mustUnderstand="1">https://login.microsoftonline.com/extSTS.srf</a:To>
    <o:Security s:mustUnderstand="1"
       xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
      <o:UsernameToken>
        <o:Username>[username]</o:Username>
        <o:Password>[password]</o:Password>
      </o:UsernameToken>
    </o:Security>
  </s:Header>
  <s:Body>
    <t:RequestSecurityToken xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust">
      <wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
        <a:EndpointReference>
          <a:Address>[endpoint]</a:Address>
        </a:EndpointReference>
      </wsp:AppliesTo>
      <t:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</t:KeyType>
      <t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType>
      <t:TokenType>urn:oasis:names:tc:SAML:1.0:assertion</t:TokenType>
    </t:RequestSecurityToken>
  </s:Body>
</s:Envelope>
  1. 注意响应数据中wsse:BinarySecurityToken节点的内容。
  2. https://YourDomain.sharepoint.com/_forms/default.aspx?wa=wsignin1.0发送Post请求。
    将“YourDomain”替换为相关值。在请求正文中提供wsse:BinarySecurityToken的内容。

响应头将包含FedAuth和rtFa cookies。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接