【问题标题】:Federated authentication in Sharepoint 2013: getting rtFa and FedAuth cookiesSharepoint 2013 中的联合身份验证:获取 rtFa 和 FedAuth cookie
【发布时间】:2014-10-12 20:45:57
【问题描述】:

场景如下:我需要对用户(使用他的大学帐户)执行联合身份验证到他大学的 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

这是一个可以在 Internet 上找到的辅助类(例如,这里是 http://blog.kloud.com.au/tag/msonlineclaimshelper/)。

这个类在正常的身份验证下工作,但在联合身份验证下失败

所以我对其进行了调整以使其在这种情况下工作。 据我了解,步骤如下:

  1. 使用大学的 STS ADFS 服务(“联合方”或 ISSUER)的用户名和密码进行身份验证 -- 这里的依赖方是 Sharepoint O365 STS ("https://login.microsoftonline.com/extSTS.srf")
  2. 如果身份验证成功,我会返回一个包含声明和安全令牌的 SAML 断言
  3. 现在,我通过传递安全令牌对 SharePoint 网站进行身份验证
  4. 如果令牌被识别,我会返回一个包含两个 cookie(FedAuth 和 rtFa)的响应

我不是这方面的专家,我出来的代码如下:

这是调用上述方法并尝试分两步从凭据中获取 FedAuth 和 rtFa 的代码(步骤 1:从 Federated Party 获取 SAML 令牌;第 2 步:将令牌从 Federated Party 传递到 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; }
    }

【问题讨论】:

    标签: c# authentication sharepoint claims-based-identity ws-federation


    【解决方案1】:

    事实上,当涉及到 SharePoint Online/Office 365 身份验证时,只有 FedAuth cookie 是强制性的。

    根据Remote Authentication in SharePoint Online Using Claims-Based Authentication

    FedAuth cookie 启用联合授权,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 (uisng MsOnlineClaimsHelper class) 检索 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 检索 FormDigest(使用 SharePointOnlineCredentials class

    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; }
     }
    

    【讨论】:

    • 我的问题是,当我尝试从共享点下载文件时(通过 HttpClient 向诸如 link 之类的 URL 执行 HTTP 请求),只有当我同时提供 FedAuth和 rtFa cookie。 FedAuth 是不够的。因此,我必须获取 rtFa cookie 或在没有它的情况下使该请求工作。 Ps:我不知道FormDigest是什么。
    • 实际上,当我成功地使用 FedAuth 和 rtFA 下载文件时,我将注意力转向了我认为的问题根源:无法获取 rtFa cookie。我会更新我的问题。
    • 答案已更新,请参阅示例的修改版本。我已经测试过并且对我来说很好
    • 谢谢,它有效!因此,SPOIDCRL 和 FedAuth cookie 之间似乎存在差异。更简单的方法是什么?实际上,我使用这种方法是因为我无法在 Windows 应用商店应用程序中使用 SharePoint 客户端库(以及其他库)。
    • 太棒了!此处显示的另一个选项stackoverflow.com/a/23687279/1375553,但在该示例中它也使用 SharePoint 客户端库。
    【解决方案2】:

    我根据https://stackoverflow.com/users/1375553/vadim-gremyachev的回答https://github.com/nddipiazza/SharepointOnlineCookieFetcher创建了一个github项目 使用可以生成这些 cookie 的项目。

    它有适用于 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

    提示时输入密码

    stdout 的结果将有SPOIDCRL cookie。

    【讨论】:

      【解决方案3】:

      对于my purposes,我仍然需要 FedAuth 和 rtFa cookie。我尝试只使用 FedAuth,但没有这两者就无法工作。 Another developer 确认他看到了同样的行为。

      注意:旧版身份验证必须为 enabled in your tenant 才能正常工作。

      这里是a thread,用于帮助获取 FedAuth 和 rtFa。

      1. 使用以下正文向 https://login.microsoftonline.com/extSTS.srf 发送 Post 请求。
        将用户名、密码、端点地址替换为相关值。
      <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. 将 Post 请求发送到 https://YourDomain.sharepoint.com/_forms/default.aspx?wa=wsignin1.0
        将 'YourDomain 替换为相关值。在请求正文中提供 wsse:BinarySecurityToken 内容。

      响应标头将包含 FedAuth 和 rtFa cookie。

      【讨论】:

        【解决方案4】:

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

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

        我已经在这里将我的示例测试项目上传到 github:https://github.com/OceanAirdrop/SharePointOnlineGetFedAuthAndRtfaCookie

        这是我所做的:

        步骤 01:导航到 WebBrowser 控件中的 SharePoint 网址

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

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

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

        接下来,覆盖WebBrowser 控件中的Navigated 事件。这让您知道页面已完全加载。

        现在,这是皱纹!! FedAuth cookie 是用 HTTPOnly 标志编写的,这意味着它们不能从 .NET Framework 访问。这意味着如果您尝试访问 WebBrowser 控件的 cookie,您将得到空字符串!

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

        因此,要解决此问题,您需要在 WININET.dll 中调用 InternetGetCookieEx。我从here 获取代码。这是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 cookie,我们可以继续使用HttpClient 调用我们需要的任何andpoint。在我的情况下,调用许多包含图像的端点。代码如下所示:

        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) 
            { 
                    
            }
        }
        

        就是这样。它就像一个魅力。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2014-10-09
          • 2023-04-03
          • 2011-09-02
          • 2017-08-06
          • 1970-01-01
          • 2015-07-22
          • 2014-06-21
          • 2012-10-23
          相关资源
          最近更新 更多