我目前正在使用Xamarin Forms开发应用。我的开发环境是Mac OS,Visual Studio和C#。我正在开发的应用程序将与Web服务连接。该服务是FamilySearch,这是一个家谱网站。
我一直在编写一些代码来发送请求并处理来自其服务器的响应。我写了一个我认为格式正确的请求,但收到的回复表明“未经授权”。
然后我决定将我的代码运行在.NET Core控制台应用程序中,以避免使用Xamarin Forms的开销。我发送了相同的确切请求,字节为字节。这样做时,我得到了一个成功的响应(状态代码200)。
因此,我有2个相同的HTTP请求,一个是从Xamarin Forms应用程序中的iOS模拟器发送的,另一个是从.NET Core应用程序中的控制台发送的。他们从服务器接收不同的响应。知道为什么会这样吗?
这是一些代码,因此您可以了解我在做什么。首先,我设置了一些HttpClient对象(一个针对他们的身份验证服务器,另一个针对服务器处理其他调用):
HttpClient _identity_host = new HttpClient();
HttpClient _platform_host = new HttpClient();
_identity_host.BaseAddress = new System.Uri("https://identint.familysearch.org");
_platform_host.BaseAddress = new System.Uri("https://api-integ.familysearch.org");
_identity_host.DefaultRequestHeaders.Accept.Add(
new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
_platform_host.DefaultRequestHeaders.Accept.Add(
new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
设置HttpClient对象后,我调用此net函数登录到我的用户帐户。 此功能在Xamarin Forms应用程序和.NET Core控制台应用程序中都很成功:
public async void AttemptLogin(string username, string password)
{
//Form the web request
Dictionary<string, string> login_content_pairs = new Dictionary<string, string>()
{
{ "password", _password },
{ "grant_type", "password" },
{ "client_id", _application_id },
{ "username", _username }
};
string login_content = this.ToQueryString(login_content_pairs, false);
StringContent content = new StringContent(login_content, Encoding.UTF8, "application/x-www-form-urlencoded");
var result = await _identity_host.PostAsync("/cis-web/oauth2/v3/token", content);
if (result.IsSuccessStatusCode)
{
var token_json = await result.Content.ReadAsStringAsync();
JObject parsed_json = JObject.Parse(token_json);
if (parsed_json.ContainsKey("access_token"))
{
_access_token = (string)parsed_json["access_token"];
}
else if (parsed_json.ContainsKey("token"))
{
_access_token = (string)parsed_json["token"];
}
string k = (string)parsed_json["token"];
//Set the authorization header on the platform host object
_platform_host.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _access_token);
_successful_login = true;
}
}
最后,在登录尝试完成后,我然后使用我的授权令牌从他们的服务器请求一些东西:
public async void GetCurrentPerson()
{
var result = await _platform_host.GetAsync("/platform/tree/current-person");
if (result.IsSuccessStatusCode)
{
var token_json = await result.Content.ReadAsStringAsync();
JObject parsed_json = JObject.Parse(token_json);
}
}
上面的GET请求是返回两个不同响应的请求 - 取决于我是使用带有Xamarin Forms的iOS模拟器还是使用.NET Core控制台应用程序。
以下是来自Visual Studio调试器的GET请求的ToString():
{
Method: GET,
RequestUri: 'https://api-integ.familysearch.org/platform/tree/persons/L5FY-BQW',
Version: 1.1,
Content: <null>,
Headers:
{
Accept: application/json
Authorization: Bearer MY_AUTHORIZATION_TOKEN
}
}
在控制台应用中,我收到了以下回复:
{
StatusCode: 200,
ReasonPhrase: 'OK',
Version: 1.1,
Content: System.Net.Http.NoWriteNoSeekStreamContent,
Headers:
{
Cache-Control: no-transform, must-revalidate, max-age=0
Date: Wed, 04 Apr 2018 01:40:13 GMT
ETag: "137412002955880000"
Server: Apache-Coyote/1.1
Vary: Accept
Vary: Accept-Language
Vary: Accept-Encoding
Vary: Expect
Vary: Accept-Encoding
Warning: 199 FamilySearch Best Practice Violation: Should specify versioned media type in Accept header, e.g. one of [ "application/x-fs-v1+xml", "application/x-fs-v1+json", "application/atom+xml", "application/x-gedcomx-atom+json", "application/x-gedcomx-v1+xml", "application/x-gedcomx-v1+json" ].
X-PROCESSING-TIME: 184
Connection: keep-alive
Allow: OPTIONS
Allow: HEAD
Allow: GET
Allow: POST
Allow: DELETE
Content-Location: /tree/persons/L5FY-BQW
Content-Type: application/json
Last-Modified: Sat, 24 Mar 2018 16:04:55 GMT
Content-Length: 6479
}
}
虽然来自Xamarin Forms应用程序的相同请求使用iOS模拟器会产生以下响应:
{
StatusCode: 401,
ReasonPhrase: 'Unauthorized',
Version: 1.1,
Content: System.Net.Http.StreamContent,
Headers:
{
Cache-Control: no-cache, no-store, no-transform, must-revalidate, max-age=0
Date: Wed, 04 Apr 2018 01:45:02 GMT
Link: <https://integration.famil...
}
}
401 Unauthorized响应的内容如下:
{
"errors" : [ {
"code" : 401,
"message" : "Unable to read tf person.",
"stacktrace" : "GET http://tf.integ.us-east-1.dev.fslocal.org/tf/person/L5FY-BQW?oneHops=none returned a response status of 401 Unauthorized: { "401" : "Unauthorized" }"
} ]
}
非常感谢任何有助于理解出错的帮助。谢谢!
答案 0 :(得分:0)
想出来!我终于能够让Charles Proxy工作,这样我就可以窥探来自iOS模拟器的HTTP SSL流量。事实证明,我的URL请求最初导致重定向,当它尝试遵循重定向时,授权标头在我不知情的情况下被剥离了请求。
以下Stack Overflow问题帮我解决了问题:Authorization header is lost on redirect
我的代码现在看起来像这样:
public async Task<int> GetCurrentPerson()
{
var result = await _platform_host.GetAsync(_current_person_uri);
//Check to see if the result was a "401 Unauthorized"
if (result.StatusCode == HttpStatusCode.Unauthorized)
{
//If so, the "Authorization" header was likely stripped after a redirect
//We will simply follow the redirect URL by calling GetAsync, and the
//authorization header should be placed back on.
//contains the final location after following the redirect.
var finalRequestUri = result.RequestMessage.RequestUri;
//detect that a redirect actually did occur.
if (finalRequestUri != _current_person_uri)
{
// check that we can trust the host we were redirected to.
if (IsHostTrusted(finalRequestUri))
{
// Reissue the request. The DefaultRequestHeaders configured
//on the client will be used, so we don't have to set them again.
result = await _platform_host.GetAsync(finalRequestUri);
}
}
}
//Now check to see if the status is successful
if (result.IsSuccessStatusCode)
{
... handle JSON that was returned in response ...
}
... rest of function ...
}
private bool IsHostTrusted(Uri uri)
{
return (uri.Host == _host_address);
}