Я публикую это по дороге домой, так что простите за отсутствие кода, но я постараюсь быть как можно более подробным и добавить код, когда смогу, сегодня вечером. По сути, у меня есть нативное приложение для реагирования, использующее избыточность и аксиомы. Краткий обзор (следующий код) может объяснить, что я делаю что-то не так.
Serviceapi.js Создает и экспортирует базовые аксиомы с базовым URL.
const ServiceApi = axios.create({
baseURL: BASE_URL,
responseType: 'json'
});
AuthReducer.js При входе в систему заголовок авторизации устанавливается вручную с помощью метода post. Это работает как на Android, так и на iOS, возвращается логин, и я использую заголовок авторизации.
return {
type: PERFORM_LOGIN,
payload: {
user: {
name: username
},
request: {
url: '/login',
method: 'post',
headers: {
'Authorization': 'Basic ' + basicAuth
}
}
}
При входе в систему я возвращаю следующее действие redux-axios, вы можете видеть, что я установил заголовок: Авторизация вручную, это прекрасно работает.
// On login success, set the authInterceptor responsible for adding headers
authInterceptor = ServiceApi.interceptors.request.use((config) => {
console.info(`Attaching Authorization to header ${basicAuth}`);
config.headers.common.Authorization = basicAuth;
return config;
}, (error) => {
Promise.reject(error);
});
При выходе из системы я очищаю перехватчик. Я решил добавлять и удалять при входе и выходе из системы вместо того, чтобы всегда иметь их там просто так. Это может быть проблемой, но для Android это нормально
// Clear the auth interceptor
ServiceApi.interceptors.request.eject(authInterceptor);
Опять же, все это отлично работает на Android. И, похоже, работает на ios. Когда я отлаживаю перехватчик, он вызывается и устанавливает заголовок.
Но я получаю обратно 403 на ios. Изучив запрос более подробно, можно заметить большую разницу между заголовком android в запросе и заголовком ios в запросе. Остальная часть объекта запроса одинакова, различается только объект _header между ios и android.
Android-запрос
_headers:
accept: "application/json, text/plain, */*"
authorization: "Basic <correct base64 value>"
content-type: "application/json;charset=utf-8"
__proto__: Object
Запрос IOS
_headers:
accept: (...)
authorization: (...)
content-type: (...)
get accept: ƒ ()
set accept: ƒ ()
get authorization: ƒ ()
set authorization: ƒ ()
get content-type: ƒ ()
set content-type: ƒ ()
__proto__: Object
С различиями, устанавливая точку останова при просмотре консоли для error.request._headers.authorization;, я получаю то же содержимое «Basic:», что и заголовок Android.
index.php Бэкэнд-сервис представляет собой файл php, который выполняет $_SERVER['PHP_AUTH_USER'], который завершается ошибкой 403, если не установлен, что и происходит. У меня нет доступа к php, мне только что сказали, что это то, что он использует.
Еще раз приношу извинения за то, что не предоставил код, но я сделаю это, когда у меня будет шанс позже. Может быть, мне нужно что-то дополнительно установить для ios? Или, может быть, php для ios нужен дополнительный заголовок?
Код для подражания.
РЕДАКТИРОВАТЬ Обновлено с кодом, надеюсь, я не оставил ни одной закодированной информации для входа.
РЕДАКТИРОВАТЬ 2 При дальнейшем расследовании похоже, что это связано с apache/PHP, а не с react-native/axios. Я собрал экспресс-сервер, который имитировал ту же проверку, что и PHP: - Найдите заголовок авторизации - Распечатать - Возврат обратно 403 или 200 с данными на основе этого
При запуске, указывающем на http://локальный: 3000, используя точно такое же приложение в эмуляторе, я получаю то, что ожидаю. Чтобы добавить к этому, когда я на эмуляторе, я не могу войти в действующий URL-адрес (хотя я мог бы на обычном устройстве), я получаю ту же ошибку 403, но на этот раз немного раньше.
РЕДАКТИРОВАТЬ 3
Чтобы предоставить дополнительную информацию с сервера, вот три запроса, которые мне удалось зарегистрировать:
1) Это из эмулятора IOS iPhone8 против экспресс-сервера:
accept:"application/json, text/plain, */*"
accept-encoding:"gzip, deflate"
accept-language:"en-us"
authorization:"Basic <base 64 encoding>"
connection:"keep-alive"
content-length:"0"
host:"localhost:3000"
user-agent:"MobileApp/1 CFNetwork/978.0.7 Darwin/18.5.
2) Это тот же эмулятор для apache/PHP (5.3.3), мы видим, что заголовок авторизации отсутствует.
Accept: application/json, text/plain, */*
User-Agent: MobileApp/1 CFNetwork/978.0.7 Darwin/18.5.0
Accept-Language: en-us
Accept-Encoding: br, gzip, deflate
Connection: keep-alive
3) Это с Android на apache/PHP (5.3.3):
authorization: Basic <Base 64 encoding>
Host: api.serviceurl.com
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.12.1
Редактировать 4 Итак, поиграв некоторое время и погуглив, выяснилось, что проблема связана с Zend Framework и fastcgi, которые автоматически удаляют заголовок авторизации. Странно то, что он делает это только из IOS, а не из Android, что на самом деле не имеет смысла.
Что мы заметили в журналах, так это то, что он принимает Android и Postman как POST, но регистрирует запросы IOS как GET. Я не совсем уверен, что с этим, но, кажется, это еще одно отличие. Я обновил задачу, чтобы использовать zend в качестве тега. Существует ряд статей SO о решении этой проблемы с помощью ReWriteMod в apache/zend, поэтому я сначала попробую их и посмотрю, решит ли это проблему.
** Редактировать 5** До сих пор мы пытались следовать статьям SO, которые просят добавить следующее (Заголовок авторизации отсутствует в django rest_framework, виноват ли apache?):
SetEnvIfNoCase Authorization ^(.*) -e=PHP_HTTP_AUTH
RewriteCond %{HTTP:Authorization} ^(.*)
RewriteRule .* - [e=HTTP_AUTHORIZATION:%1]
что приводит к следующему:
// IOS
_SERVER[PHP_HTTP_AUTH] = <blank>
_SERVER[HTTP_AUTHORIZATION] = <blank>
// Android
_SERVER[PHP_HTTP_AUTH] = Username
_SERVER[HTTP_AUTHORIZATION] = Basic <Base65 encoded>
_SERVER[PHP_HTTP_PW] = Password
Итак, мы знаем, что авторизация заголовков доходит до Apache, но теперь она проходит как пустая. Есть несколько других ответов SO, которые я изучаю, но поиск продолжается...
Изменить 6
Решено (иш)
https. Для входа используется тот же клиент axios. Единственная разница в том, что я делаю a. Опубликовать(/login, {headers:{}}) вручную. В то время как для всех остальных запросов я использую перехватчик. Я мог бы переписать клиент так, чтобы он постоянно устанавливал пользовательский проход вручную, но я решил, что лучше использовать перехватчик. Прошу прощения, я тоже сижу на телефоне еще пару часов. Так что простите за опечатки.
Ах я вижу. Не могу сказать, что знаком с концепцией перехватчика в react-native. Это часть redux? Звучание все больше и больше похоже на это, будет легче отлаживать, когда код будет опубликован :)
Перехватчик - вещь аксиома. Используя что-то похожее на gist.github.com/srph/38f67a10e991b6cb2d83 this. Но я установил перехватчик в действии редуктора LOGIN_SUCCESS вместо того, чтобы хранить его в клиентском файле asios. Я ценю внешний вид. Выложу код в т-минус перед сном.






Оказывается, это была косая черта в конце запроса для IOS. Мне удалось найти эту ссылку https://github.com/square/retrofit/issues/1037, где проблема была описана как:
For those interested: We are using Django as our backend and by default when you do not provide a trailing slash on the endpoint Django redirects from the non-slash endpoint to the slash endpoint.
Сейчас мы не используем Django, но, видимо, для нашей конфигурации Zend это было та же проблема - Android смог перенаправить без проблем, а IOS - нет. В другом комментарии к заданию говорится:
OkHttp strips the "Authorization" header when redirected across hosts (connections) via a 3xx response from the original host.
Что не кажется точным, так как Android использовал OkHttp и работал нормально. Похоже, у IOS, использующего Darwin, была проблема.
РЕДАКТИРОВАТЬ
Я забыл еще кое-что из своего исходного поста, мне также пришлось изменить свой перехватчик со строки config.headers.common.Authorization = ... на config.headers.Authorization = ..., который почему-то сохранил корпус. Первоначальный способ преобразовал авторизацию в авторизацию, а последний сохранил ее как авторизацию. Не уверен, что это было проблемой, но я все равно сделал это.
// On login success, set the authInterceptor responsible for adding headers
authInterceptor = ServiceApi.interceptors.request.use((config) => {
console.info(`Attaching Authorization to header ${basicAuth}`);
config.headers.Authorization = basicAuth;
return config;
}, (error) => {
Promise.reject(error);
});
Является ли URL-адрес, к которому вы подключаетесь, за http или https? Я помню, что в
info.plistбыла настройка, которая указывает URL-адреса, к которым вы можете подключиться с помощью незащищенных вызовов. Может быть что-то в этом духе?