[OAuth Series] 使用 Access Token 存取 Private APIs

在前一回完成了整个 OAuth 验证与授权的流程后,程序应该可以成功取得 Access Token 以及 Access Token Secret,只要有这两个数据,应用程序就可以以 Access Token 所代表的使用者来与 OAuth 服务的 Private API 来交互,大多数 OAuth 服务上的 API 都会需要先取得 Access Token 后才可以使用 (虽然还是有少数可以不用啦),所以这篇文章就来说明怎么使用 Access Token 来存取 Private APIs。


在前一回完成了整个 OAuth 验证与授权的流程后,程序应该可以成功取得 Access Token 以及 Access Token Secret,只要有这两个数据,应用程序就可以以 Access Token 所代表的使用者来与 OAuth 服务的 Private API 来交互,大多数 OAuth 服务上的 API 都会需要先取得 Access Token 后才可以使用 (虽然还是有少数可以不用啦),所以这篇文章就来说明怎么使用 Access Token 来存取 Private APIs。

延续前一回文章所用的数据结构以及函数,前面已经做了 RenderOAuthAuthorizationHeaderForRequestToken() 以及 RenderOAuthAuthorizationHeaderForAccessToken(),现在我在程序中加入一个新的函数,以支持 Private API 的调用:

public virtual string RenderOAuthAuthorizationHeaderForPerformRequest()
{
   StringBuilder headerBuilder = new StringBuilder();
   Dictionary oauthDictionary =
       this.GetDictionaryFromOAuthData(OAuthTagRenderPhraseEnum.PerformRequest, true);

    oauthDictionary["oauth_signature"] = OAuthUtility.UrlEncode(oauthDictionary["oauth_signature"]);

    foreach (KeyValuePair oauthParamItem in oauthDictionary)
   {
       if (headerBuilder.Length == 0)
           headerBuilder.Append(oauthParamItem.Key + "="" + oauthParamItem.Value + """);
       else
           headerBuilder.Append("," + oauthParamItem.Key + "="" + oauthParamItem.Value + """);
   }

    return "OAuth " + headerBuilder.ToString();
}

在存取 Private API 时,需要的 OAuth 参数有 oauth_nonce, oauth_consumer_key, oauth_timestamp, oauth_signature_method, oauth_version, oauth_signature, oauth_token 等,RenderOAuthAuthorizationHeaderForPerformRequest() 所做的事,就是将这些参数组成一个 base string,再使用 Consumer Key 和 Access Token Secret 产生签章后,附加到 OAuth Authorization 标头中。

准备好 OAuth 参数后,就可以撰写出 PerformRequest() 来调用 OAuth Private APIs:

public override string PerformRequest(string RequestUrl)
{
   HttpWebRequest request = HttpWebRequest.Create(RequestUrl) as HttpWebRequest;
   HttpWebResponse response = null;
   string responseData = null;
   ServicePointManager.Expect100Continue = false;

    request.Method = "GET";

    this._oauthDataRepository.MakeNewRequestParams();
   this._oauthDataRepository.PrepareRequestSignature(
       new Uri(RequestUrl), "GET", OAuthTagRenderPhraseEnum.PerformRequest);

    request.Headers.Add("Authorization",
       this._oauthDataRepository.RenderOAuthAuthorizationHeaderForPerformRequest());

    try
   {
       response = request.GetResponse() as HttpWebResponse;

        StreamReader sr = new StreamReader(response.GetResponseStream());
       responseData = sr.ReadToEnd();
       sr.Close();
   }
   catch (WebException we)
   {
       response = we.Response as HttpWebResponse;

        StreamReader sr = new StreamReader(response.GetResponseStream());
       responseData = sr.ReadToEnd();
       sr.Close();

        if (response.StatusCode == HttpStatusCode.Unauthorized)
           throw new OAuthUnauthorizedException("ERROR_OAUTH_UNAUTHORIZED",
               this._oauthDataRepository.GetOAuthSiguatureBase(
                   new Uri(RequestUrl), "GET", OAuthTagRenderPhraseEnum.PerformRequest),
               this._oauthDataRepository.Signature,
               responseData);
       else
           throw new OAuthNetworkException("ERROR_NETWORK_PROBLEM", response.StatusCode, responseData);

    }
   catch (Exception e)
   {
       throw new OAuthException("ERROR_EXCEPION_OCCURRED", e);
   }
   finally
   {
       response.Close();
   }

    response = null;
   request = null;

    return responseData;
}

不过 OAuth 的参数在每个 OAuth 服务间有一些不同的设定,例如 Yahoo 要在 OAuth 参数中加一个 realm 的设定,遇到这种情况时就只能依服务来修改程序,不能全部沿用。

另外,Access Token 和 Access Token Secret 默认是永不过期的,除非使用者撤销对应用程序的授权,因此应用程序可以将取得的 Access Token 和 Access Token Secret 存起来,日后直接使用 Access Token 即可存取 OAuth 服务的 Private APIs,保存的方法可以是 Database, XML, Text Files 或其他任何可保存的方法,例如下列程序:

public class PersistXmlProvider : IPersistProvider
{
   private static string AccessTokensFileName = "OAuthAccessTokens.dat";

    public void PersistAccessToken(string ProviderType, string AccessToken, string TokenSecret, DateTime ExpireDate)
   {
       XmlDocument accessTokenStorage = new XmlDocument();

        if (File.Exists(Environment.CurrentDirectory + @"" + PersistXmlProvider.AccessTokensFileName))
           accessTokenStorage.Load(Environment.CurrentDirectory + @"" + PersistXmlProvider.AccessTokensFileName);
       else
           accessTokenStorage.LoadXml("");

        XmlNode nodeAccessToken = accessTokenStorage.DocumentElement.SelectSingleNode("//tokens/token[@provider='" + ProviderType + "']");
       XmlNode nodeProviderType = null, nodeAccessTokenValue = null, nodeAccessTokenSecret = null, nodeExpiresDateTime = null;

        if (nodeAccessToken == null)
       {
           // insert token
           nodeAccessToken = accessTokenStorage.CreateNode(XmlNodeType.Element, "token", null);
           nodeProviderType = accessTokenStorage.CreateNode(XmlNodeType.Attribute, "provider", null);
           nodeAccessTokenValue = accessTokenStorage.CreateNode(XmlNodeType.Attribute, "tokenValue", null);
           nodeAccessTokenSecret = accessTokenStorage.CreateNode(XmlNodeType.Attribute, "tokenSecret", null);
           nodeExpiresDateTime = accessTokenStorage.CreateNode(XmlNodeType.Attribute, "expireDate", null);

            nodeProviderType.Value = ProviderType;
           nodeAccessTokenValue.Value = AccessToken;
           nodeAccessTokenSecret.Value = TokenSecret;
           nodeExpiresDateTime.Value = ExpireDate.ToString("yyyy/MM/dd HH:mm:ss"); ;

            nodeAccessToken.Attributes.SetNamedItem(nodeProviderType);
           nodeAccessToken.Attributes.SetNamedItem(nodeAccessTokenValue);
           nodeAccessToken.Attributes.SetNamedItem(nodeAccessTokenSecret);
           nodeAccessToken.Attributes.SetNamedItem(nodeExpiresDateTime);
           accessTokenStorage.DocumentElement.AppendChild(nodeAccessToken);
       }
       else
       {
           // update token
           nodeProviderType = nodeAccessToken.Attributes.GetNamedItem("provider");
           nodeAccessTokenValue = nodeAccessToken.Attributes.GetNamedItem("tokenValue");
           nodeAccessTokenSecret = nodeAccessToken.Attributes.GetNamedItem("tokenSecret");
           nodeExpiresDateTime = nodeAccessToken.Attributes.GetNamedItem("expireDate");

            nodeProviderType.Value = ProviderType;
           nodeAccessTokenValue.Value = AccessToken;
           nodeAccessTokenSecret.Value = TokenSecret;
           nodeExpiresDateTime.Value = ExpireDate.ToString("yyyy/MM/dd HH:mm:ss"); ;
       }

        accessTokenStorage.Save(Environment.CurrentDirectory + @"" + PersistXmlProvider.AccessTokensFileName);
       accessTokenStorage = null;
   }

    public void RetriveAccessToken(string ProviderType, out string AccessToken, out string TokenSecret, out DateTime ExpireDate)
   {
       XmlDocument accessTokenStorage = new XmlDocument();

        if (!File.Exists(Environment.CurrentDirectory + @"" + PersistXmlProvider.AccessTokensFileName))
       {
           accessTokenStorage = null;
           throw new OAuthException("ERROR_PERSISTED_ACCESS_TOKEN_IS_NOT_EXIST");
       }
       else
           accessTokenStorage.Load(Environment.CurrentDirectory + @"" + PersistXmlProvider.AccessTokensFileName);

        XmlNode nodeAccessToken = accessTokenStorage.DocumentElement.SelectSingleNode("//tokens/token[@provider='" + ProviderType + "']");

        if (nodeAccessToken == null)
       {
           accessTokenStorage = null;
           throw new OAuthException("ERROR_PERSISTED_ACCESS_TOKEN_IS_NOT_EXIST");
       }
       else
       {
           XmlNode nodeAccessTokenValue = nodeAccessToken.Attributes.GetNamedItem("tokenValue");
           XmlNode nodeAccessTokenSecret = nodeAccessToken.Attributes.GetNamedItem("tokenSecret");
           XmlNode nodeExpiresDateTime = nodeAccessToken.Attributes.GetNamedItem("expireDate");
           DateTime expireDate = DateTime.ParseExact(nodeExpiresDateTime.Value, "yyyy/MM/dd HH:mm:ss", null);

            if (expireDate < DateTime.Now)
           {
               accessTokenStorage = null;
               throw new OAuthException("ERROR_PERSISTED_ACCESS_TOKEN_IS_EXPIRED");
           }
           else
           {
               AccessToken = nodeAccessTokenValue.Value;
               TokenSecret = nodeAccessTokenSecret.Value;
               ExpireDate = expireDate;
           }              
       }

        accessTokenStorage = null;
   }

    public void RemoveAccessToken(string ProviderType)
   {
       XmlDocument accessTokenStorage = new XmlDocument();

        if (!File.Exists(Environment.CurrentDirectory + @"" + PersistXmlProvider.AccessTokensFileName))
       {
           accessTokenStorage = null;
           return;
       }
       else
           accessTokenStorage.Load(Environment.CurrentDirectory + @"" + PersistXmlProvider.AccessTokensFileName);

        XmlNode nodeAccessToken = accessTokenStorage.DocumentElement.SelectSingleNode("//tokens/token[@provider='" + ProviderType + "']");

        if (nodeAccessToken == null)
       {
           accessTokenStorage = null;
           return;
       }
       else
           accessTokenStorage.DocumentElement.RemoveChild(nodeAccessToken);

        accessTokenStorage.Save(Environment.CurrentDirectory + @"" + PersistXmlProvider.AccessTokensFileName);
       accessTokenStorage = null;
   }

    public bool IsAccessTokenValid(string ProviderType)
   {
       XmlDocument accessTokenStorage = new XmlDocument();

        if (!File.Exists(Environment.CurrentDirectory + @"" + PersistXmlProvider.AccessTokensFileName))
       {
           accessTokenStorage = null;
           return false;
       }
       else
           accessTokenStorage.Load(Environment.CurrentDirectory + @"" + PersistXmlProvider.AccessTokensFileName);

        XmlNode nodeAccessToken = accessTokenStorage.DocumentElement.SelectSingleNode("//tokens/token[@provider='" + ProviderType + "']");
       bool result = false;

        if (nodeAccessToken == null)
       {
           result = false;
       }
       else
       {
           XmlNode nodeExpiresDateTime = nodeAccessToken.Attributes.GetNamedItem("expireDate");
           DateTime expireDate = DateTime.ParseExact(nodeExpiresDateTime.Value, "yyyy/MM/dd HH:mm:ss", null);

            if (expireDate < DateTime.Now)
               result = false;
           else
               result = true;
       }

        accessTokenStorage = null;
       return result;
   }
}

只是就目前我个人所知的,Facebook 和 Yahoo 的 Access Token 是有到期时间的,Facebook 在不指定参数的情况下,默认是有到期时间的;Yahoo 则是默认 3600 秒 (一小时),若超过时只能再用 Refresh Access Token (http://developer.yahoo.com/oauth/guide/oauth-refreshaccesstoken.html) 的方式来刷新存取时间。