2014-09-09 6 views
5

मैं माइक्रोसॉफ्ट लाइव (.NET WebClient?) पर कैसे लॉग ऑन कर सकता हूं और बिंग विज्ञापन API कॉल करने के लिए टोकन प्राप्त करने के लिए OAuth प्रक्रिया को स्वचालित कर सकता हूं?बिंग विज्ञापन OAuth स्वचालन केवल .NET का उपयोग कर?

मेरा प्रश्न How do I get an OAuth request_token from live.com? के समान है। हालांकि, मैं एक अन्य विज्ञापन बिंग विज्ञापन खातों से जुड़ा हुआ एक बिंग विज्ञापन सुपर व्यवस्थापक खाते के संदर्भ का उपयोग कर एक हेडलेस विंडोज सेवा (सी #, .NET 4.5.2) बना रहा हूं। विचार प्रमाणीकरण करना है, ऑथ बिट्स प्राप्त करें, और फिर 3:00 बजे बिट्स का उपयोग करके कॉल करें। कुछ खाते "प्रतिस्पर्धा" करते हैं, उदाहरण के लिए समूह ए को समूह बी से डेटा नहीं दिखाना चाहिए, इसलिए एप्लिकेशन को हर किसी के लिए डेटा प्राप्त करना और उसे फ़िल्टर करना और इसे वितरित करना रात भर कई व्यवसायिक समस्याओं को हल करता है।

मुझे चिंतित है कि यदि लाइव अनुभव की समस्या है, या हमारे आवेदन किसी भी कारण से विस्तारित समय के लिए नीचे है, तो हमें डेटा फिर से प्राप्त करने के लिए मैन्युअल रूप से पुनः प्रमाणीकरण करना होगा। क्रेडेंशियल्स का रखरखाव और प्रबंधन अब अतिरिक्त ओवरहेड है (यह एक एंटरप्राइज़ पर्यावरण के लिए है) जो एक इंट्रानेट वेब साइट/पेज का रूप लेना होगा ताकि जूनियर/यूनिनेटेड लोगों को काम करने के लिए काम करने की अनुमति मिल सके (परीक्षण को भूलना न भूलें और प्रलेखन)। इसके विपरीत, Google उन समूहों के लिए महत्वपूर्ण जोड़े का उपयोग करने का विकल्प प्रदान करता है जिन्हें पूरी तरह से स्वचालित तरीके से काम करने की आवश्यकता होती है। ऐसा लगता है कि ट्विटर के OAuth2 कार्यान्वयन को GUI लॉगऑन के बिना स्वचालित किया जा सकता है। ऐसा लगता है कि अन्य बिंग सेवाएं (जैसे Translation) को वेब क्लाइंट के साथ भी स्वचालित किया जा सकता है।

मेरे पास पहले से ही माइक्रोसॉफ्ट खाता नाम और पासवर्ड है, और बिंग विज्ञापन ऐप जीयूआई में "local-mydomain.com" सेट का कॉलबैक यूआरएल है (और स्थानीय-mydomain.com के लिए एक HOSTS प्रविष्टि है)।

माइक्रोसॉफ्ट sample काम पर प्रतीत होता है, लेकिन यह एमएस वेब ब्राउज़र नियंत्रण को स्वचालित करता है, उम्मीद है कि उपयोगकर्ता जीयूआई में प्रमाण पत्र इनपुट करने के लिए, और फिर टोकन दिया जाता है। ऐसा करने के लिए उपयोगकर्ताओं को सुपर व्यवस्थापक खाता देना एक विकल्प नहीं है। डेटा अपलोड/डाउनलोड करने के लिए प्रमाणीकृत करने के लिए उपयोगकर्ता को 3:00 बजे उठने की अपेक्षा करना एक विकल्प नहीं है। किसी उपयोगकर्ता को खेत में किसी सर्वर पर "कुछ चलाने" के लिए डेस्कटॉप पहुंच प्राप्त करने की अपेक्षा करना एक विकल्प नहीं है।

सभी ओथ विचारों की सराहना की।

धन्यवाद।

partial class OAuthForm : Form 
    { 
     private static OAuthForm _form; 
     private static WebBrowser _browser; 

     private static string _code; 
     private static string _error; 

     // When you register your application, the Client ID is provisioned. 

     private const string ClientId = "000redacted000"; 

     // Request-related URIs that you use to get an authorization code, 
     // access token, and refresh token. 

     private const string AuthorizeUri = "https://login.live.com/oauth20_authorize.srf"; 
     private const string TokenUri = "https://login.live.com/oauth20_token.srf"; 
     private const string DesktopUri = "https://login.live.com/oauth20_desktop.srf"; 
     private const string RedirectPath = "/oauth20_desktop.srf"; 
     private const string ConsentUriFormatter = "{0}?client_id={1}&scope=bingads.manage&response_type=code&redirect_uri={2}"; 
     private const string AccessUriFormatter = "{0}?client_id={1}&code={2}&grant_type=authorization_code&redirect_uri={3}"; 
     private const string RefreshUriFormatter = "{0}?client_id={1}&grant_type=refresh_token&redirect_uri={2}&refresh_token={3}"; 

     // Constructor 

     public OAuthForm(string uri) 
     { 
      InitializeForm(uri); 
     } 

     [STAThread] 
     static void Main() 
     { 
      // Create the URI to get user consent. Returns the authorization 
      // code that is used to get an access token and refresh token. 

      var uri = string.Format(ConsentUriFormatter, AuthorizeUri, ClientId, DesktopUri); 

      _form = new OAuthForm(uri); 

      // The value for "uri" is 
      // https://login.live.com/oauth20_authorize.srf?client_id=000redacted000&scope=bingads.manage&response_type=code&redirect_uri=https://login.live.com/oauth20_desktop.srf 



      _form.FormClosing += form_FormClosing; 
      _form.Size = new Size(420, 580); 

      Application.EnableVisualStyles(); 

      // Launch the form and make an initial request for user consent. 
      // For example POST /oauth20_authorize.srf? 
      //     client_id=<ClientId> 
      //     &scope=bingads.manage 
      //     &response_type=code 
      //     &redirect_uri=https://login.live.com/oauth20_desktop.srf HTTP/1.1 

      Application.Run(_form); // <!---------- Problem is here. 
            // I do not want a web browser window to show, 
            // I need to automate the part of the process where 
            // a user enters their name/password and are 
            // redirected. 

      // While the application is running, browser_Navigated filters traffic to identify 
      // the redirect URI. The redirect's query string will contain either the authorization 
      // code if the user consented or an error if the user declined. 
      // For example https://login.live.com/oauth20_desktop.srf?code=<code> 



      // If the user did not give consent or the application was 
      // not registered, the authorization code will be null. 

      if (string.IsNullOrEmpty(_code)) 
      { 
       Console.WriteLine(_error); 
       return; 
      } 
+0

'माइक्रोसॉफ्ट नमूना काम करने के लिए प्रकट होता है, लेकिन यह एमएस वेब ब्राउज़र नियंत्रण को स्वचालित, उम्मीद इनपुट साख के लिए एक उपयोगकर्ता GUI' में आप उस कोड स्निपेट दिखाने शायद आप साख Enum गलत तरीके से करता है, तो एक है का उपयोग कर रहे सकते हैं आवश्यक .. – MethodMan

+0

@DJKRAZE, समस्या एनम नहीं है, यह है कि एक टोकन प्राप्त करने के लिए एमएस नमूने में एक Winform + वेब ब्राउज़र नियंत्रण की आवश्यकता है। मेरे पास एक ऑथ टोकन प्राप्त करने के लिए रैक में बैठे सर्वर पर एक GUI COM ऑब्जेक्ट पॉप-अप नहीं हो सकता है। मेरी वास्तविक समस्या यह नहीं जानती है कि एक टोकन वापस पाने के लिए शुद्ध कोड में क्रेडेंशियल कैसे भेजना है - बिना जीयूआई के। धन्यवाद। – Snowy

+0

क्या आप दिखा सकते हैं कि अब तक आपके पास क्या है .. यह सामान्य रूप से कठिन नहीं लगता है कि ज्यादातर टोकन या उपयोगकर्ता नाम \ डोमेन पासवर्ड का उपयोग करते हैं, उदाहरण के लिए 'PropertyName.UseDefaultCredentials' का प्रस्ताव है उदाहरण के लिए आप कोड – MethodMan

उत्तर

5

तुम जो भी करते हैं, "सुपर व्यवस्थापक" कम से कम एक बार पर लॉग ऑन करने, एक ब्राउज़र का उपयोग करना होगा:

यहाँ शुरू कोड है। आप अपनी सेवा में एक साधारण वेब पेज होस्ट करके ऐसा कर सकते हैं, या आप इसे सेटअप प्रक्रिया के हिस्से के रूप में कर सकते हैं। लाइव नमूने आपको दिखाते हैं कि यह कैसे करें।

कोड अनुदान का उपयोग करके "सुपर व्यवस्थापक" लॉग ऑन हो जाने के बाद, आपको एक टोकन और रीफ्रेश टोकन एक्सेस प्राप्त होता है। मुझे यकीन नहीं है कि लाइव एक्सेस टोकन कब तक वैध है, लेकिन शायद यह एक रात के दौड़ के लिए पर्याप्त लॉग है। एक सुरक्षित जगह में ताज़ा टोकन सहेजें। अगली रात, आप एक नए एक्सेस टोकन और एक नया रीफ्रेश टोकन द्वारा रीफ्रेश टोकन का आदान-प्रदान करके शुरू करते हैं। दोबारा, आप अगली रात के लिए इस नए ताज़ा टोकन को बचाओ।

आप इस प्रक्रिया को हमेशा के लिए चलते रह सकते हैं, जब तक कि "सुपर व्यवस्थापक" आपके ऐप को दिए गए प्राधिकरण को निरस्त नहीं करता है।

अद्यतन:

कुछ OAuth 2.0 सर्वर का समर्थन "संसाधन मालिक पासवर्ड साख अनुदान", http://tools.ietf.org/html/rfc6749 पर RFC देखें। यदि लाइव सर्वर इसका समर्थन करता है, तो यह कोड अनुदान का एक विकल्प होगा जिसके लिए ब्राउज़र की आवश्यकता नहीं होती है। हालांकि, सर्वर का भी समर्थन करता है, मैं सुरक्षा कारणों से इसके खिलाफ अनुशंसा करता हूं, क्योंकि इसे सर्वर पर आपके "सुपर एडमिन" पासवर्ड को स्टोर करने की आवश्यकता होती है।अगर कोई पासवर्ड पकड़ लेता है, तो उसके पास खाते तक पूर्ण पहुंच है, और इसके द्वारा सुरक्षित सभी संसाधन हैं। यदि आप पासवर्ड बदलते हैं तो यह भी टूट जाएगा। कोड अनुदान में ये समस्याएं नहीं हैं।

आपका प्रश्न बताता है कि आप चाहते हैं या इस "सुपर व्यवस्थापक" के रूप में चलाने की आवश्यकता है। "क्लाइंट क्रेडेंशियल्स अनुदान" का उपयोग करने के लिए एक अन्य विकल्प हो सकता है। हालांकि, इसे सर्वर पर संग्रहीत करने के लिए क्लाइंट राइट की आवश्यकता होती है (जैसे पासवर्ड प्रमाण-पत्र अनुदान के साथ)। इसके अलावा, इसे अभी भी सुपर व्यवस्थापक को क्लाइंट को अधिकृत करने की आवश्यकता है, और स्वयं को ब्राउज़र का उपयोग करके कोड अनुदान की आवश्यकता होती है।

आप पूछते हैं कि कोड अनुदान को ब्राउज़र की आवश्यकता क्यों है, आप किसी ब्राउज़र इंटरैक्शन को अनुकरण करने के लिए किसी प्रकार की स्क्रीन स्क्रैपिंग का उपयोग क्यों नहीं कर सकते। सबसे पहले, आप स्क्रीन पर भविष्यवाणी नहीं कर सकते हैं जो उपयोगकर्ता को दिखाया जाएगा। ये स्क्रीन बिना किसी सूचना के बदलती हैं। सबसे महत्वपूर्ण बात यह है कि, उपयोगकर्ता विकल्प और इतिहास के आधार पर, सर्वर अलग-अलग स्क्रीन दिखाता है। उदाहरण के लिए, उपयोगकर्ता दो-कारक प्रमाणीकरण चालू कर सकता है। अंतिम लेकिन कम से कम नहीं, आप ब्राउज़र खोलने का ऑब्जेक्ट क्यों करते हैं? यह अनुकरण करने की कोशिश करने से शायद आसान हो जाएगा।

अंत में, ये "सुपर व्यवस्थापक" उपयोगकर्ता आपके आवेदन को अपना पासवर्ड देने के लिए ऑब्जेक्ट कर सकते हैं, क्योंकि वे वास्तव में नहीं जानते कि आप इसके साथ क्या कर रहे हैं (आप अपने आप के सर्वर पर भेज रहे हैं, जहां तक वे जानते हैं)। ब्राउज़र के साथ कोड अनुदान का उपयोग करके, वे जानते हैं कि आपका एप्लिकेशन कभी भी अपना पासवर्ड नहीं देखता है (तरह - - आप ब्राउज़र ईवेंट या कुछ पर सुन सकते हैं, जब तक कि ब्राउज़र नियंत्रण एक अलग प्रक्रिया में नहीं चलता है, जैसे कि आपके नियंत्रण में नहीं, जैसे कि विंडोज 8 वेब प्रमाणीकरण ब्रोकर)। आपका एप्लिकेशन केवल उन स्कॉप्स के साथ टोकन प्राप्त करता है जिन्हें वे अधिकृत करते हैं।

+0

धन्यवाद दिखा सकते हैं क्रिस। एक बात जो मुझे समझ में नहीं आती है वह है कि ब्राउजर की आवश्यकता क्यों है।मुझे लगता है कि वेब क्लाइंट कुछ भी कर सकता है जो मानव कर सकता है (प्रमाण पत्र पोस्ट करना, फॉर्म भरना आदि), और सर्वर सर्वर ओएथ एक नई समस्या नहीं है और हल हो गई है। मैंने सोचा कि जब मुझे http://weblog.west-wind.com/posts/2013/Jun/06/Setting-up-and-using-Bing-translate-API- सेवा-for- माचिन- अनुवाद लेकिन शायद नहीं। – Snowy

+1

@nowy मैंने अपना जवाब अपडेट किया है। –

+0

@ क्रिस, क्या मैं "सुपर एडमिन" से उत्पन्न सभी विज्ञापन खाते के लिए एकल रीफ्रेश टोकन का उपयोग कर सकता हूं। –

4

मेरे लिए इस समस्या पर कुछ घंटों खर्च करने और सेवा से बिंग से कनेक्ट करने के लिए स्वचालित रूप से कोई समाधान खोजने के बाद। यहां बताया गया है Watin अद्भुत Watin

पहले हड़पने का उपयोग कर काम करते हैं और Nuget के माध्यम से अपने समाधान के लिए इसे जोड़ने जाएगा।

फिर माइक्रोसॉफ्ट से टोकन के पूरे हथियाने को स्वचालित करने के लिए निम्न कोड का उपयोग करें (मेरा उदाहरण कंसोल एप्लिकेशन में एक उदाहरण के रूप में काम करता है)। यह सही नहीं है क्योंकि यह एक नमूना है लेकिन यह काम करेगा।

आपको तत्व आईडी की जांच को दोबारा जांचना चाहिए, अगर वे बदल गए हैं, तो वे हार्ड कोड किए गए हैं - यदि आप उत्पादन वातावरण में इसका उपयोग करने जा रहे हैं तो आम तौर पर सभी हार्ड कोडिंग को हटा दें।

मैं नहीं चाहता था कि किसी और को इसके माध्यम से जाना पड़े।

सबसे पहले यह एक कोड को पकड़ता है जिसे तब टोकन को पकड़ने के लिए उपयोग किया जाता है, जैसे ओएथ 2.0 विनिर्देश की आवश्यकता होती है।

using System; 
using System.Collections.Generic; 
using System.Net; 
using System.IO; 
using System.Runtime.Serialization; 
using System.Runtime.Serialization.Json; 
using System.Text; 
using WatiN.Core.Native; 
using WatiN.Core; 

namespace LouiesOAuthCodeGrantFlow 
{ 
    // Using access tokens requires that you register your application and that 
    // the user gives consent to your application to access their data. This 
    // example uses a form and WebBrowser control to get the user's consent. 
    // The control and form require a single-threaded apartment. 

partial class LouiesBingOAuthAutomation 
{ 

    private static LouiesBingOAuthAutomation _form; 

    private static string _code; 
    private static string _error; 

    //your going to want to put these in a secure place this is for the sample 
    public const string UserName = "your microsoft user name"; 
    public const string Password = "<your microsoft account password"; 

    // When you register your application, the Client ID is provisioned. 
    //get your clientid https://developers.bingads.microsoft.com/Account 
    private const string ClientId = "<your client id>"; 

    // Request-related URIs that you use to get an authorization code, 
    // access token, and refresh token. 

    private const string AuthorizeUri = "https://login.live.com/oauth20_authorize.srf"; 
    private const string TokenUri = "https://login.live.com/oauth20_token.srf"; 
    private const string DesktopUri = "https://login.live.com/oauth20_desktop.srf"; 
    private const string RedirectPath = "/oauth20_desktop.srf"; 
    private const string ConsentUriFormatter = "{0}?client_id={1}&scope=bingads.manage&response_type=code&redirect_uri={2}";//&displayNone 
    private const string AccessUriFormatter = "{0}?client_id={1}&code={2}&grant_type=authorization_code&redirect_uri={3}"; 
    private const string RefreshUriFormatter = "{0}?client_id={1}&grant_type=refresh_token&redirect_uri={2}&refresh_token={3}"; 

    // Constructor 

    public LouiesBingOAuthAutomation(string uri) 
    { 
     InitializeForm(uri); 
    } 

    [STAThread] 
    static void Main() 
    { 

     var uri = string.Format(ConsentUriFormatter, AuthorizeUri, ClientId, DesktopUri); 
     _form = new LouiesBingOAuthAutomation(uri); 

     if (string.IsNullOrEmpty(_code)) 
     { 
      Console.WriteLine(_error); 
      return; 
     } 

     uri = string.Format(AccessUriFormatter, TokenUri, ClientId, _code, DesktopUri); 
     AccessTokens tokens = GetAccessTokens(uri); 

     Console.WriteLine("Access token expires in {0} minutes: ", tokens.ExpiresIn/60); 
     Console.WriteLine("\nAccess token: " + tokens.AccessToken); 
     Console.WriteLine("\nRefresh token: " + tokens.RefreshToken); 


     uri = string.Format(RefreshUriFormatter, TokenUri, ClientId, DesktopUri, tokens.RefreshToken); 
     tokens = GetAccessTokens(uri); 

     Console.WriteLine("Access token expires in {0} minutes: ", tokens.ExpiresIn/60); 
     Console.WriteLine("\nAccess token: " + tokens.AccessToken); 
     Console.WriteLine("\nRefresh token: " + tokens.RefreshToken); 
    } 


    private void InitializeForm(string uri) 
    { 

     using (var browser = new IE(uri)) 
     { 
      var page = browser.Page<MyPage>(); 
      page.PasswordField.TypeText(Password); 
      try 
      { 
       StringBuilder js = new StringBuilder(); 
       js.Append(@"var myTextField = document.getElementById('i0116');"); 
       js.Append(@"myTextField.setAttribute('value', '"+ UserName + "');"); 
       browser.RunScript(js.ToString()); 
       var field = browser.ElementOfType<TextFieldExtended>("i0116"); 
       field.TypeText(UserName); 
      } 
      catch (Exception ex) 
      { 
       Console.Write(ex.Message + ex.StackTrace); 
      } 
      page.LoginButton.Click(); 
      browser.WaitForComplete(); 
      browser.Button(Find.ById("idBtn_Accept")).Click(); 
      var len = browser.Url.Length - 43; 
      string query = browser.Url.Substring(43, len); 

      if (query.Length == 50) 
      { 
       if (!string.IsNullOrEmpty(query)) 
       { 
        Dictionary<string, string> parameters = ParseQueryString(query, new[] { '&', '?' }); 

        if (parameters.ContainsKey("code")) 
        { 
         _code = parameters["code"]; 
        } 
        else 
        { 
         _error = Uri.UnescapeDataString(parameters["error_description"]); 
        } 
       } 
      } 

     } 

    } 

    // Parses the URI query string. The query string contains a list of name-value pairs 
    // following the '?'. Each name-value pair is separated by an '&'. 

    private static Dictionary<string, string> ParseQueryString(string query, char[] delimiters) 
    { 
     var parameters = new Dictionary<string, string>(); 

     string[] pairs = query.Split(delimiters, StringSplitOptions.RemoveEmptyEntries); 

     foreach (string pair in pairs) 
     { 
      string[] nameValue = pair.Split(new[] { '=' }); 
      parameters.Add(nameValue[0], nameValue[1]); 
     } 

     return parameters; 
    } 

    // Gets an access token. Returns the access token, access token 
    // expiration, and refresh token. 

    private static AccessTokens GetAccessTokens(string uri) 
    { 
     var responseSerializer = new DataContractJsonSerializer(typeof(AccessTokens)); 
     AccessTokens tokenResponse = null; 

     try 
     { 
      var realUri = new Uri(uri, UriKind.Absolute); 

      var addy = realUri.AbsoluteUri.Substring(0, realUri.AbsoluteUri.Length - realUri.Query.Length); 
      var request = (HttpWebRequest)WebRequest.Create(addy); 

      request.Method = "POST"; 
      request.ContentType = "application/x-www-form-urlencoded"; 

      using (var writer = new StreamWriter(request.GetRequestStream())) 
      { 
       writer.Write(realUri.Query.Substring(1)); 
      } 

      var response = (HttpWebResponse)request.GetResponse(); 

      using (Stream responseStream = response.GetResponseStream()) 
      { 
       if (responseStream != null) 
        tokenResponse = (AccessTokens)responseSerializer.ReadObject(responseStream); 
      } 
     } 
     catch (WebException e) 
     { 
      var response = (HttpWebResponse)e.Response; 

      Console.WriteLine("HTTP status code: " + response.StatusCode); 
     } 

     return tokenResponse; 
    } 

} 



public class MyPage : WatiN.Core.Page 
{ 
    public TextField PasswordField 
    { 
     get { return Document.TextField(Find.ByName("passwd")); } 
    } 

    public WatiN.Core.Button LoginButton 
    { 
     get { return Document.Button(Find.ById("idSIButton9")); } 
    } 
} 

[ElementTag("input", InputType = "text", Index = 0)] 
[ElementTag("input", InputType = "password", Index = 1)] 
[ElementTag("input", InputType = "textarea", Index = 2)] 
[ElementTag("input", InputType = "hidden", Index = 3)] 
[ElementTag("textarea", Index = 4)] 
[ElementTag("input", InputType = "email", Index = 5)] 
[ElementTag("input", InputType = "url", Index = 6)] 
[ElementTag("input", InputType = "number", Index = 7)] 
[ElementTag("input", InputType = "range", Index = 8)] 
[ElementTag("input", InputType = "search", Index = 9)] 
[ElementTag("input", InputType = "color", Index = 10)] 
public class TextFieldExtended : TextField 
{ 
    public TextFieldExtended(DomContainer domContainer, INativeElement element) 
     : base(domContainer, element) 
    { 
    } 

    public TextFieldExtended(DomContainer domContainer, ElementFinder finder) 
     : base(domContainer, finder) 
    { 
    } 

    public static void Register() 
    { 
     Type typeToRegister = typeof(TextFieldExtended); 
     ElementFactory.RegisterElementType(typeToRegister); 
    } 
} 


// The grant flow returns more fields than captured in this sample. 
// Additional fields are not relevant for calling Bing Ads APIs or refreshing the token. 

[DataContract] 
class AccessTokens 
{ 
    [DataMember] 
    // Indicates the duration in seconds until the access token will expire. 
    internal int expires_in = 0; 

    [DataMember] 
    // When calling Bing Ads service operations, the access token is used as 
    // the AuthenticationToken header element. 
    internal string access_token = null; 

    [DataMember] 
    // May be used to get a new access token with a fresh expiration duration. 
    internal string refresh_token = null; 

    public string AccessToken { get { return access_token; } } 
    public int ExpiresIn { get { return expires_in; } } 
    public string RefreshToken { get { return refresh_token; } } 
} 
} 
संबंधित मुद्दे