是否可以在没有重定向服务器的情况下使用 OAuth 2.0?
Is it possible to use OAuth 2.0 without a redirect server?
我正在尝试创建一个与 SurveyMonkey API.
交互的基于 Java 的本地客户端
SurveyMonkey 需要使用 OAuth 2.0 的长期访问令牌,我对此不是很熟悉。
我已经用谷歌搜索了几个小时,我认为答案是否定的,但我只是想确定一下:
我是否可以编写一个与 SurveyMonkey 交互的简单 Java 客户端,无需在某些云中设置我自己的重定向服务器?
我觉得必须拥有自己的在线服务才能接收 OAuth 2.0 生成的不记名令牌。有没有可能我不能让 SurveyMonkey 直接向我的客户发送不记名令牌?
如果我要在某处设置自己的自定义 Servlet,并将其用作 redirect_uri,那么正确的流程如下:
- Java-客户端从 SurveyMonkey 请求承载令牌,带有
redirect_uri 是我自己的自定义 servlet URL。
- SurveyMonkey 将令牌发送到我的自定义 servlet URL。
- Java-客户端轮询自定义 servlet URL 直到令牌可用?
这是正确的吗?
不完全是,OAuth 流程的重点是用户(您代表的客户端访问数据)需要授予您访问其数据的权限。
见authentication instructions。您需要将用户发送到 OAuth 授权页面:
https://api.surveymonkey.net/oauth/authorize?api_key<your_key>&client_id=<your_client_id>&response_type=code&redirect_uri=<your_redirect_uri>
这将向用户显示一个页面,告诉他们您请求访问他们帐户的哪些部分(例如,查看他们的调查、查看他们的回复等)。一旦用户通过单击该页面上的 "Authorize" 批准,SurveyMonkey 将自动转到您设置为重定向 URI 的任何内容(确保上面 url 中的那个与您在设置中设置的匹配您的应用程序)与代码。
因此,如果您的重定向 URL 是 https://example.com/surveymonkey/oauth
,SurveyMonkey 将使用代码将用户重定向到 URL:
https://example.com/surveymonkey/oauth?code=<auth_code>
您需要获取该代码,然后通过使用以下 post 参数向 https://api.surveymonkey.net/oauth/token?api_key=<your_api_key>
发出 POST 请求来将其交换为访问令牌:
client_secret=<your_secret>
code=<auth_code_you_just_got>
redirect_uri=<same_redirect_uri_as_before>
grant_type=authorization_code
这将 return 一个访问令牌,然后您可以使用该访问令牌访问用户帐户上的数据。您不会将访问令牌提供给您用于访问用户帐户的用户。不需要轮询或任何东西。
如果您只是访问自己的帐户,则可以使用应用设置页面中提供的访问令牌。否则,如果不设置您自己的重定向服务器,就无法为用户获取访问令牌(除非所有用户都与您在同一组中,即同一帐户下的多个用户;但我不会深入探讨)。 SurveyMonkey 需要一个地方在用户授权后向您发送代码,您不能只请求一个。
是的,可以在没有回调的情况下使用 OAuth2 URL。
RFC6749 introduces several flows. The Implicit and Authorization Code grant types require a redirect URI. However the Resource Owner Password Credentials 授权类型没有。
自 RFC6749 以来,已发布的其他规范不 要求任何重定向 URI:
- RFC7522:用于 OAuth 2.0 客户端身份验证和授权授予的安全断言标记语言 (SAML) 2.0 配置文件
- RFC7523:JSON OAuth 2.0 客户端身份验证和授权授予的 Web 令牌 (JWT) 配置文件
- RFC8628:OAuth 2.0 设备授权授予
无论如何,如果上述资助类型不符合您的需求,没有什么能阻止您创建 custom grant type。
你做需要实现一些东西来充当redirect_uri,它不一定需要托管在你的客户端之外的其他地方(如你所说,在一些云中)。
我对 Java 和 Servelets 不是很熟悉,但如果我假设正确,那将是可以处理 http://localhost:some_port 的东西。在这种情况下,您描述的流程是正确的。
我在 C# 中成功实现了相同的流程。这是实现该流程的 class。希望对你有帮助。
class OAuth2Negotiator
{
private HttpListener _listener = null;
private string _accessToken = null;
private string _errorResult = null;
private string _apiKey = null;
private string _clientSecret = null;
private string _redirectUri = null;
public OAuth2Negotiator(string apiKey, string address, string clientSecret)
{
_apiKey = apiKey;
_redirectUri = address.TrimEnd('/');
_clientSecret = clientSecret;
_listener = new HttpListener();
_listener.Prefixes.Add(address + "/");
_listener.AuthenticationSchemes = AuthenticationSchemes.Anonymous;
}
public string GetToken()
{
var url = string.Format(@"https://api.surveymonkey.net/oauth/authorize?redirect_uri={0}&client_id=sm_sunsoftdemo&response_type=code&api_key=svtx8maxmjmqavpavdd5sg5p",
HttpUtility.UrlEncode(@"http://localhost:60403"));
System.Diagnostics.Process.Start(url);
_listener.Start();
AsyncContext.Run(() => ListenLoop(_listener));
_listener.Stop();
if (!string.IsNullOrEmpty(_errorResult))
throw new Exception(_errorResult);
return _accessToken;
}
private async void ListenLoop(HttpListener listener)
{
while (true)
{
var context = await listener.GetContextAsync();
var query = context.Request.QueryString;
if (context.Request.Url.ToString().EndsWith("favicon.ico"))
{
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
context.Response.Close();
}
else if (query != null && query.Count > 0)
{
if (!string.IsNullOrEmpty(query["code"]))
{
_accessToken = await SendCodeAsync(query["code"]);
break;
}
else if (!string.IsNullOrEmpty(query["error"]))
{
_errorResult = string.Format("{0}: {1}", query["error"], query["error_description"]);
break;
}
}
}
}
private async Task<string> SendCodeAsync(string code)
{
var GrantType = "authorization_code";
//client_secret, code, redirect_uri and grant_type. The grant type must be set to “authorization_code”
var client = new HttpClient();
client.BaseAddress = new Uri("https://api.surveymonkey.net");
var request = new HttpRequestMessage(HttpMethod.Post, string.Format("/oauth/token?api_key={0}", _apiKey));
var formData = new List<KeyValuePair<string, string>>();
formData.Add(new KeyValuePair<string, string>("client_secret", _clientSecret));
formData.Add(new KeyValuePair<string, string>("code", code));
formData.Add(new KeyValuePair<string, string>("redirect_uri", _redirectUri));
formData.Add(new KeyValuePair<string, string>("grant_type", GrantType));
formData.Add(new KeyValuePair<string, string>("client_id", "sm_sunsoftdemo"));
request.Content = new FormUrlEncodedContent(formData);
var response = await client.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
_errorResult = string.Format("Status {0}: {1}", response.StatusCode.ToString(), response.ReasonPhrase.ToString());
return null;
}
var data = await response.Content.ReadAsStringAsync();
if (data == null)
return null;
Dictionary<string, string> tokenInfo = JsonConvert.DeserializeObject<Dictionary<string, string>>(data);
return(tokenInfo["access_token"]);
}
}
我正在尝试创建一个与 SurveyMonkey API.
交互的基于 Java 的本地客户端SurveyMonkey 需要使用 OAuth 2.0 的长期访问令牌,我对此不是很熟悉。
我已经用谷歌搜索了几个小时,我认为答案是否定的,但我只是想确定一下:
我是否可以编写一个与 SurveyMonkey 交互的简单 Java 客户端,无需在某些云中设置我自己的重定向服务器?
我觉得必须拥有自己的在线服务才能接收 OAuth 2.0 生成的不记名令牌。有没有可能我不能让 SurveyMonkey 直接向我的客户发送不记名令牌?
如果我要在某处设置自己的自定义 Servlet,并将其用作 redirect_uri,那么正确的流程如下:
- Java-客户端从 SurveyMonkey 请求承载令牌,带有 redirect_uri 是我自己的自定义 servlet URL。
- SurveyMonkey 将令牌发送到我的自定义 servlet URL。
- Java-客户端轮询自定义 servlet URL 直到令牌可用?
这是正确的吗?
不完全是,OAuth 流程的重点是用户(您代表的客户端访问数据)需要授予您访问其数据的权限。
见authentication instructions。您需要将用户发送到 OAuth 授权页面:
https://api.surveymonkey.net/oauth/authorize?api_key<your_key>&client_id=<your_client_id>&response_type=code&redirect_uri=<your_redirect_uri>
这将向用户显示一个页面,告诉他们您请求访问他们帐户的哪些部分(例如,查看他们的调查、查看他们的回复等)。一旦用户通过单击该页面上的 "Authorize" 批准,SurveyMonkey 将自动转到您设置为重定向 URI 的任何内容(确保上面 url 中的那个与您在设置中设置的匹配您的应用程序)与代码。
因此,如果您的重定向 URL 是 https://example.com/surveymonkey/oauth
,SurveyMonkey 将使用代码将用户重定向到 URL:
https://example.com/surveymonkey/oauth?code=<auth_code>
您需要获取该代码,然后通过使用以下 post 参数向 https://api.surveymonkey.net/oauth/token?api_key=<your_api_key>
发出 POST 请求来将其交换为访问令牌:
client_secret=<your_secret>
code=<auth_code_you_just_got>
redirect_uri=<same_redirect_uri_as_before>
grant_type=authorization_code
这将 return 一个访问令牌,然后您可以使用该访问令牌访问用户帐户上的数据。您不会将访问令牌提供给您用于访问用户帐户的用户。不需要轮询或任何东西。
如果您只是访问自己的帐户,则可以使用应用设置页面中提供的访问令牌。否则,如果不设置您自己的重定向服务器,就无法为用户获取访问令牌(除非所有用户都与您在同一组中,即同一帐户下的多个用户;但我不会深入探讨)。 SurveyMonkey 需要一个地方在用户授权后向您发送代码,您不能只请求一个。
是的,可以在没有回调的情况下使用 OAuth2 URL。 RFC6749 introduces several flows. The Implicit and Authorization Code grant types require a redirect URI. However the Resource Owner Password Credentials 授权类型没有。
自 RFC6749 以来,已发布的其他规范不 要求任何重定向 URI:
- RFC7522:用于 OAuth 2.0 客户端身份验证和授权授予的安全断言标记语言 (SAML) 2.0 配置文件
- RFC7523:JSON OAuth 2.0 客户端身份验证和授权授予的 Web 令牌 (JWT) 配置文件
- RFC8628:OAuth 2.0 设备授权授予
无论如何,如果上述资助类型不符合您的需求,没有什么能阻止您创建 custom grant type。
你做需要实现一些东西来充当redirect_uri,它不一定需要托管在你的客户端之外的其他地方(如你所说,在一些云中)。
我对 Java 和 Servelets 不是很熟悉,但如果我假设正确,那将是可以处理 http://localhost:some_port 的东西。在这种情况下,您描述的流程是正确的。
我在 C# 中成功实现了相同的流程。这是实现该流程的 class。希望对你有帮助。
class OAuth2Negotiator
{
private HttpListener _listener = null;
private string _accessToken = null;
private string _errorResult = null;
private string _apiKey = null;
private string _clientSecret = null;
private string _redirectUri = null;
public OAuth2Negotiator(string apiKey, string address, string clientSecret)
{
_apiKey = apiKey;
_redirectUri = address.TrimEnd('/');
_clientSecret = clientSecret;
_listener = new HttpListener();
_listener.Prefixes.Add(address + "/");
_listener.AuthenticationSchemes = AuthenticationSchemes.Anonymous;
}
public string GetToken()
{
var url = string.Format(@"https://api.surveymonkey.net/oauth/authorize?redirect_uri={0}&client_id=sm_sunsoftdemo&response_type=code&api_key=svtx8maxmjmqavpavdd5sg5p",
HttpUtility.UrlEncode(@"http://localhost:60403"));
System.Diagnostics.Process.Start(url);
_listener.Start();
AsyncContext.Run(() => ListenLoop(_listener));
_listener.Stop();
if (!string.IsNullOrEmpty(_errorResult))
throw new Exception(_errorResult);
return _accessToken;
}
private async void ListenLoop(HttpListener listener)
{
while (true)
{
var context = await listener.GetContextAsync();
var query = context.Request.QueryString;
if (context.Request.Url.ToString().EndsWith("favicon.ico"))
{
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
context.Response.Close();
}
else if (query != null && query.Count > 0)
{
if (!string.IsNullOrEmpty(query["code"]))
{
_accessToken = await SendCodeAsync(query["code"]);
break;
}
else if (!string.IsNullOrEmpty(query["error"]))
{
_errorResult = string.Format("{0}: {1}", query["error"], query["error_description"]);
break;
}
}
}
}
private async Task<string> SendCodeAsync(string code)
{
var GrantType = "authorization_code";
//client_secret, code, redirect_uri and grant_type. The grant type must be set to “authorization_code”
var client = new HttpClient();
client.BaseAddress = new Uri("https://api.surveymonkey.net");
var request = new HttpRequestMessage(HttpMethod.Post, string.Format("/oauth/token?api_key={0}", _apiKey));
var formData = new List<KeyValuePair<string, string>>();
formData.Add(new KeyValuePair<string, string>("client_secret", _clientSecret));
formData.Add(new KeyValuePair<string, string>("code", code));
formData.Add(new KeyValuePair<string, string>("redirect_uri", _redirectUri));
formData.Add(new KeyValuePair<string, string>("grant_type", GrantType));
formData.Add(new KeyValuePair<string, string>("client_id", "sm_sunsoftdemo"));
request.Content = new FormUrlEncodedContent(formData);
var response = await client.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
_errorResult = string.Format("Status {0}: {1}", response.StatusCode.ToString(), response.ReasonPhrase.ToString());
return null;
}
var data = await response.Content.ReadAsStringAsync();
if (data == null)
return null;
Dictionary<string, string> tokenInfo = JsonConvert.DeserializeObject<Dictionary<string, string>>(data);
return(tokenInfo["access_token"]);
}
}