Как использовать OAuth для подключения к API-интерфейсу Etrade?

Недавно E-Trade выпустила свой API и предоставила техническую документацию

Ответ 1

Мне удалось подключиться с помощью DevDefined OAuth Library, но мне пришлось сделать некоторые твики для источника, чтобы он работал правильно. Я разветкил репо, чтобы вы могли скачать src, который я использовал, и построить вам .dll.

Repo: GitHub

Пример Класс:

 public abstract class BaseOAuthRepository
{

    private static string REQUEST_URL = "https://etws.etrade.com/oauth/request_token";
    private static string AUTHORIZE_URL = "https://us.etrade.com/e/t/etws/authorize";
    private static string ACCESS_URL = "https://etws.etrade.com/oauth/access_token";

    private readonly TokenBase _tokenBase;
    private readonly string _consumerSecret;

    protected BaseOAuthRepository(TokenBase tokenBase, 
                                  string consumerSecret)
    {
        _tokenBase = tokenBase;
        _consumerSecret = consumerSecret;
    }

    public TokenBase MyTokenBase
    {
        get { return _tokenBase; }
    }

    public string MyConsumerSecret
    {
        get { return _consumerSecret; }
    }


    public OAuthSession CreateSession()
    {
        var consumerContext = new OAuthConsumerContext
        {
            ConsumerKey = MyTokenBase.ConsumerKey,
            ConsumerSecret = MyConsumerSecret,
            SignatureMethod = SignatureMethod.HmacSha1,
            UseHeaderForOAuthParameters = true,
            CallBack = "oob"
        };

        var session = new OAuthSession(consumerContext, REQUEST_URL, AUTHORIZE_URL, ACCESS_URL);    
        return session;
    }

    public IToken GetAccessToken(OAuthSession session)
    {
        IToken requestToken = session.GetRequestToken();
        string authorizationLink = session.GetUserAuthorizationUrlForToken(MyTokenBase.ConsumerKey, requestToken);
        Process.Start(authorizationLink);
        Console.Write("Please enter pin from browser: ");
        string pin = Console.ReadLine();
        IToken accessToken = session.ExchangeRequestTokenForAccessToken(requestToken, pin.ToUpper());

        return accessToken;
    }

    public string GetResponse(OAuthSession session, string url)
    {
        IToken accessToken = MyTokenBase;

        var response = session.Request(accessToken).Get().ForUrl(url).ToString();
        return response;
    }

    public XDocument GetWebResponseAsXml(HttpWebResponse response)
    {
        XmlReader xmlReader = XmlReader.Create(response.GetResponseStream());
        XDocument xdoc = XDocument.Load(xmlReader);
        xmlReader.Close();
        return xdoc;
    }

    public string GetWebResponseAsString(HttpWebResponse response)
    {
        Encoding enc = System.Text.Encoding.GetEncoding(1252);
        StreamReader loResponseStream = new
        StreamReader(response.GetResponseStream(), enc);
        return loResponseStream.ReadToEnd();
    }
}

Ответ 2

Вот код, который я использовал для подключения к API ETrade (протестирован и работает).

Одно предупреждение: вам необходимо реализовать собственное хранилище токенов пользователя. Я не включил это здесь, так как созданный мной код сильно зависит от домена.

Сначала я добавил DotNetOpenAuth в проект и создал ETradeConsumer (он происходит от DotNetOpenAuth WebConsumer):

EtradeConsumer.cs

public static class ETradeConsumer
{
    public static string AccessUrl 
    { 
        get 
        { 
            return "https://etws.etrade.com/oauth/access_token"; 
        } 
    }

    public static string RequestUrl 
    { 
        get 
        { 
            return "https://etws.etrade.com/oauth/request_token"; 
        } 
    }

public static string UserAuthorizedUrl 
    {  
        get 
        { 
            return "https://us.etrade.com/e/t/etws/authorize"; 
        } 
    }
private static readonly ServiceProviderDescription ServiceProviderDescription = new ServiceProviderDescription()
{
    AccessTokenEndpoint = new MessageReceivingEndpoint(AccessUrl, HttpDeliveryMethods.PostRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
    ProtocolVersion = ProtocolVersion.V10a,
    RequestTokenEndpoint = new MessageReceivingEndpoint(RequestUrl, HttpDeliveryMethods.PostRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
    TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement() },
    UserAuthorizationEndpoint = new MessageReceivingEndpoint(new Uri(UserAuthorizedUrl), HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest)
};

public static DesktopConsumer CreateConsumer(IConsumerTokenManager tokenManager)
{
    return new DesktopConsumer(ServiceProviderDescription, tokenManager);
}

public static Uri PrepareRequestAuthorization(DesktopConsumer consumer, out string requestToken)
{
    if (consumer == null)
    {
        throw new ArgumentNullException("consumer");
    }

        Uri authorizationUrl = consumer.RequestUserAuthorization(null, null, out requestToken);

    authorizationUrl = new Uri(string.Format("{0}?key={1}&token={2}", ServiceProviderDescription.UserAuthorizationEndpoint.Location.AbsoluteUri, consumer.TokenManager.ConsumerKey, requestToken));
    return authorizationUrl;
}

public static AuthorizedTokenResponse CompleteAuthorization(DesktopConsumer consumer, string requestToken, string userCode)
    {
    var customServiceDescription = new ServiceProviderDescription
    {
            RequestTokenEndpoint = ServiceProviderDescription.RequestTokenEndpoint,
        UserAuthorizationEndpoint =
                new MessageReceivingEndpoint(
                string.Format("{0}?key={1}&token={2}",  ServiceProviderDescription.UserAuthorizationEndpoint.Location.AbsoluteUri,
                              consumer.TokenManager.ConsumerKey, requestToken),
                HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest),
        AccessTokenEndpoint = new MessageReceivingEndpoint(
        ServiceProviderDescription.AccessTokenEndpoint.Location.AbsoluteUri + "?oauth_verifier" + userCode + string.Empty,
                HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest),
        TamperProtectionElements = ServiceProviderDescription.TamperProtectionElements,
        ProtocolVersion = ProtocolVersion.V10a
    };

    var customConsumer = new DesktopConsumer(customServiceDescription, consumer.TokenManager);
    var response = customConsumer.ProcessUserAuthorization(requestToken, userCode);
    return response;
    }

}

Во-вторых, вам нужно создать класс для управления маркерами Etrade. В качестве примера я создал следующий класс. Он управляет токенами через InMemoryCollection, но он действительно должен храниться где-то в другом месте (база данных или файл cookie или что-то такое, что пользователю не нужно аутентифицироваться/авторизироваться каждый раз). Метки ConsumerKey и ConsumerSecret - это те, что вы подписываете через Etrade:

public class ETradeTokenManager : IConsumerTokenManager 
{
    private Dictionary<string, string> tokensAndSecrets = new Dictionary<string, string>();
public string ConsumerKey { get { return "YourConsumerKey"; } }
public string ConsumerSecret { get { return "YourConsumerSecret";  } }

public string GetTokenSecret(string token)
{
    return tokensAndSecrets[token];
}

public void StoreNewRequestToken(UnauthorizedTokenRequest request, ITokenSecretContainingMessage response)
{
    tokensAndSecrets[response.Token] = response.TokenSecret;
}

public void ExpireRequestTokenAndStoreNewAccessToken(string consumerKey, string requestToken, string accessToken, string accessTokenSecret)
{
    tokensAndSecrets.Remove(requestToken);
    tokensAndSecrets[accessToken] = accessTokenSecret;
}

public TokenType GetTokenType(string token)
{
    throw new NotImplementedException();
}
}

Наконец, добавьте следующее (я использовал ASP.NET MVC 3. Ваша структура может отличаться):

public ActionResult EtradeAuthorize(string returnUrl)
   {
        var consumer = ETradeConsumer.CreateConsumer(TokenManager);
    string requestToken;
    Uri popupWindow = ETradeConsumer.PrepareRequestAuthorization(consumer, out requestToken);
    var etradeViewModel = new ETradeAuthorizeViewModel(popupWindow, requestToken);
    return View(etradeViewModel);
    }

    [HttpPost]
    public ActionResult CompleteAuthorization(FormCollection formCollection)
    {
    string accessToken = "";
    var consumer = ETradeConsumer.CreateConsumer(TokenManager);
    var authorizationReponse = ETradeConsumer.CompleteAuthorization(consumer, formCollection["requestToken"], formCollection["userCode"]);
    if (authorizationReponse != null)
    {
        accessToken = authorizationReponse.AccessToken;
    }
    var etradeViewModel = new ETradeCompleteAuthorizeViewModel(formCollection["requestToken"], formCollection["userCode"], accessToken);
    return View(etradeViewModel);
    }

Если вы получите 400 Bad Request, вытащите callbackUrl для Etrade. По какой-то причине он выдает плохую просьбу всякий раз, когда используется URL-адрес обратного вызова. Они предпочитают oob (вне диапазона). Чтобы использовать oob, установите null на URL обратного вызова в методе Consumer.Channel.Send().

Есть и другие проблемы. Эта проблема: Due to a logon delay or other issue, your authentication could not be completed at this time. Please try again. вызвана тем, что часть authorize не обрабатывается должным образом. В частности, Etrade требует, чтобы URL-адрес авторизации выглядел следующим образом:

https://us.etrade.com/e/t/etws/authorize?key={yourConsumerKey}&token={requestToken}

Спецификация OAuth требует, чтобы токен запроса был request_token={requestToken}, а не token={requestToken}.

Я не смог заставить API-интерфейс Etrade правильно разрешить с помощью WebConsumer, но как только я переключился на Desktop Consumer и сам обработал запрос, он работал правильно.

Ответ 3

Если вы   "Из-за задержки входа в систему или другой проблемы аутентификация не может быть завершена в настоящее время. Повторите попытку.",

то я думаю, что вы поместили неправильные ключи в urify.

Вы должны следовать документу, заменив соответствующие ключи в этом формате

https://us.etrade.com/e/etws/authorize?key= & токен =

Ответ 4

Чтобы использовать код класса class + GitHub из jejernig answer, я использовал следующее:

TokenBase token = new TokenBase { ConsumerKey = "oauth_consumer_key from ETRADE" }; // OAuthRepository only seems to use the consumer key
OAuthRepository rep = new OAuthRepository(token, "consumer_secret from ETRADE");
OAuthSession session = rep.CreateSession();
IToken accessToken = rep.GetAccessToken(session);

Я просто удалил abstract из BaseOAuthRepository и должен был исправить код GitHub, потому что параметры IOAuthSession.GetUserAuthorizationUrlForToken() были изменены (я изменил остальную часть кода в соответствии с параметрами интерфейса).

Я получаю ужасное сообщение Due to a logon delay or other issue, your authentication could not be completed at this time. Please try again., но это может быть связано с фактической проблемой входа в систему, которую я должен решить.

Ответ 5

Удалить файлы cookie и повторить попытку. Я не уверен, почему это происходит. Но, как только вы получите эту ошибку, если иначе вы не очистите файлы cookie, вы получите ту же ошибку. Я могу войти в систему и вызвать некоторые из служб REST.