Sharepoint 2013中的联合身份验证:获取rtFa和FedAuth cookie

met*_*ori 9 c# authentication sharepoint claims-based-identity ws-federation

方案如下:我需要对用户(使用他的大学帐户)执行联盟身份验证到他的大学的Sharepoint站点,并获得FedAuth和rtFa cookie(我必须将其传递给SharePoint REST webservices)为了访问资源).

我做了一些尝试,但每个问题至少有一个问题:

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);
Run Code Online (Sandbox Code Playgroud)

这样我设法获得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. 如果auth成功,我会收到包含声明和安全令牌的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;
        }
    }
Run Code Online (Sandbox Code Playgroud)

我设法从大学STS获得了一个SAML令牌.但是,在解析时,生成的SecurityToken没有安全密钥(即,SecurityKeys集合为空)

没有密钥,我得到GetO365BinaryTokenFromToken(),但当我尝试将令牌发送到SharePoint身份验证服务时 - 我收到以下错误:"签名令牌Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityToken没有密钥.安全性token在需要它执行加密操作的上下文中使用,但令牌不包含加密密钥.令牌类型不支持加密操作,或者特定令牌实例不包含加密密钥.检查配置以确保加密在需要加密操作的上下文中未指定禁用的令牌类型(例如,UserNameSecurityToken)(例如,支持支持令牌)."

我认为还有一些我无法直接控制的配置问题,双方(大学STS ADFS和Sharepoint STS).

我希望更多专家能够在这个过程中提供清晰度,甚至提供建议,以实际使这个场景有效.

文件下载功能

用下面的函数,我能够下载文件(给出的网址,如https://myfederatedparty.sharepoint.com/sites/MYSITE/path/myfile.pdf)发出BOTH的FedAuth和rtFa饼干.如果我没有通过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; }
    }
Run Code Online (Sandbox Code Playgroud)

Vad*_*hev 11

实际上,FedAuth在SharePoint Online/Office 365身份验证方面,只有cookie是必需的.

根据SharePoint Online中的远程身份验证使用基于声明的身份验证:

FedAuth饼干启用联合授权,以及rtFA Cookie允许从所有SharePoint网站注销用户,即使签出处理来自非SharePoint站点开始.

因此,只需提供SPOIDCRLHTTP标头即可在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);
Run Code Online (Sandbox Code Playgroud)

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

示例1:通过SharePoint 2013 REST API检索FormDigest(uisng MsOnlineClaimsHelper class)

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();
        }     
    }
}
Run Code Online (Sandbox Code Playgroud)

示例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();
        }
   }
}
Run Code Online (Sandbox Code Playgroud)

更新

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

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; }
 }
Run Code Online (Sandbox Code Playgroud)