C# Bing广告OAuth自动化仅使用.NET?
如何登录到Microsoft Live(使用.NET WebClient?)并自动执行OAuth过程,以获取令牌以进行Bing Ads API调用 我的问题类似于。但是,我正在使用Bing Ads超级管理员帐户的上下文构建(C#,.NET 4.5.2)一个无头Windows服务,该帐户链接到多个其他Bing Ads帐户。其思想是进行身份验证,获取身份验证位,然后在凌晨3:00使用这些位进行调用。一些帐户“竞争”,例如,A组不应该看到B组的数据,因此让应用程序为每个人获取数据,并在一夜之间进行过滤和分发可以解决许多业务问题 我担心的是,如果Live遇到问题,或者我们的应用程序由于任何原因长时间关闭,我们将不得不手动重新验证以再次获取数据。凭证的维护和管理现在是额外的开销(这是针对企业环境的),必须采用intranet网站/页面的形式,以允许初级/非初级人员在需要时完成工作(不要忘记测试和文档)。相比之下,谷歌提供了一个选项,可以为需要以完全自动化方式工作的组使用密钥对。Twitter的OAuth2实现似乎可以在没有GUI登录的情况下实现自动化。似乎其他必应服务(eg)也可以通过WebClient实现自动化 我已经有了Microsoft帐户名和密码,并且在Bing Ads应用程序GUI中设置了“local mydomain.com”的回调URL(并且为local mydomain.com设置了HOSTS条目) 微软似乎可以工作,但它自动化了MS Web浏览器控件,要求用户在GUI中输入凭据,然后给出令牌。将超级管理员帐户提供给用户来执行此操作不是一个选项。期望用户在凌晨3:00起床进行身份验证以上传/下载数据不是一个选项。期望用户获得服务器场中服务器的桌面访问权以“运行某些东西”不是一个选项 所有的OAuth想法都受到赞赏 谢谢 以下是启动代码:C# Bing广告OAuth自动化仅使用.NET?,c#,oauth,oauth-2.0,bing-api,C#,Oauth,Oauth 2.0,Bing Api,如何登录到Microsoft Live(使用.NET WebClient?)并自动执行OAuth过程,以获取令牌以进行Bing Ads API调用 我的问题类似于。但是,我正在使用Bing Ads超级管理员帐户的上下文构建(C#,.NET 4.5.2)一个无头Windows服务,该帐户链接到多个其他Bing Ads帐户。其思想是进行身份验证,获取身份验证位,然后在凌晨3:00使用这些位进行调用。一些帐户“竞争”,例如,A组不应该看到B组的数据,因此让应用程序为每个人获取数据,并在一夜之间进行过滤
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;
}
无论你做什么,“超级管理员”都必须使用浏览器至少登录一次。您可以通过在服务中托管一个简单的网页来实现这一点,也可以作为安装过程的一部分来实现。现场样品向您展示了如何做到这一点
一旦“超级管理员”使用代码授权登录,您将收到一个访问令牌和一个刷新令牌。我不确定liveaccess令牌的有效期有多长,但它的日志可能足够每晚运行一次。将刷新令牌保存在安全的地方。第二天晚上,首先用新的访问令牌和新的刷新令牌交换该刷新令牌。同样,您要为第二天晚上保存这个新的刷新令牌
只要“超级管理员”不撤销他对你的应用程序的授权,你就可以让这个过程永远运行下去
更新:
一些OAuth 2.0服务器支持“资源所有者密码凭据授予”,请参阅RFC。如果Live server支持这一点,它将是不需要浏览器的代码授权的替代方案。然而,即使大多数服务器支持它,出于安全原因,我也建议不要使用它,因为它需要在服务器上存储您的“超级管理员”密码。如果有人获取了密码,他们就可以完全访问该帐户以及受其保护的所有资源。如果您更改密码,它也会崩溃。代码授权没有这些问题
您的问题表明您希望或需要作为“超级管理员”运行。另一种选择可能是使用“客户端凭据授予”。但是,这也需要在服务器上存储客户机机密(与授予密码凭据一样)。此外,它仍然需要超级管理员授权客户端,这本身就需要使用浏览器授予代码
您会问为什么代码授权需要浏览器,为什么不能使用某种屏幕抓取来模拟浏览器交互。首先,您无法预测将向用户显示的屏幕。这些屏幕会在没有通知的情况下更改。更重要的是,根据用户选项和历史记录,服务器会显示不同的屏幕。例如,用户可能已启用双因素身份验证。最后但并非最不重要的一点是,为什么您反对打开浏览器?这可能比试图模仿它更容易
最后,这些“超级管理员”用户可能会反对将他们的密码提供给您的应用程序,因为他们并不真正知道您在用它做什么(据他们所知,您可能正在向自己的服务器发送密码)。通过在浏览器中使用代码授权,他们知道您的应用程序永远看不到他们的密码(某种程度上-您可以监听浏览器事件或其他内容,除非浏览器控件在不受您控制的单独进程(如Windows 8 WebAuthenticationBroker)中运行)。您的应用程序只获得一个具有其授权范围的令牌。我自己花了几个小时解决了这个问题,却完全没有找到从服务自动连接到Bing的解决方案。下面是使用奇妙的WatiN 首先抓取WatiN并通过Nuget将其添加到您的解决方案中 然后使用以下代码(我的示例以控制台应用程序为例)自动化从Microsoft获取令牌的整个过程。这不是完美的,因为这是一个样本,但它会工作 您应该仔细检查我正在使用的元素ID,以防它们发生更改,它们是硬编码的-如果要在生产环境中使用,通常会删除所有硬编码 我不想让其他任何人经历这一切 首先,它获取一个代码,然后用于获取令牌,就像OAuth2.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; } }
}
}
使用系统;
使用System.Collections.Generic;
Net系统;
使用System.IO;
使用System.Runtime。