Подтвердить что ты не робот

Почему браузер не повторно использует заголовки авторизации после аутентифицированного XMLHttpRequest?

Я разрабатываю одностраничное приложение, используя Angular. Бэкэнд предоставляет услуги REST, которые требуют базовой аутентификации. Получение index.html или любого из скриптов не требует аутентификации.

У меня странная ситуация, когда одно из моих представлений имеет <img>, где src - это URL-адрес REST API, для которого требуется аутентификация. <img> обрабатывается браузером, и у меня нет возможности установить заголовок авторизации для запроса GET, который он делает. Это заставляет браузер запрашивать учетные данные.

Я попытался исправить это, выполнив следующее:

  • Оставьте img src пустым в источнике
  • В "готовом документе" введите XMLHttpRequest в службу (/api/login) с заголовком авторизации, чтобы вызвать аутентификацию.
  • По завершении этого вызова установите атрибут img src, думая, что к тому моменту браузер узнает, что он будет включать заголовок авторизации в последующих запросах...

... но это не так. Запрос изображения исчезает без заголовков. Если я введу учетные данные, то все остальные изображения на странице верны. (Я также пробовал и Angular ng-src, но это дало тот же результат)

У меня есть два вопроса:

  • Почему браузер (IE10) не включил заголовки во все запросы после успешного XMLHttpRequest?
  • Что я могу сделать, чтобы обойти эту проблему?

@bergi попросил детали запросов. Вот они.

Запрос/api/login

GET https://myserver/dev30281_WebServices/api/login HTTP/1.1
Accept: */*
Authorization: Basic <header here>
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.2; WOW64; Trident/6.0; .NET4.0E; .NET4.0C; .NET CLR 3.5.30729; .NET CLR 2.0.50727; .NET CLR 3.0.30729)
Connection: Keep-Alive

Ответ (/api/login)

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 4
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 20 Dec 2013 14:44:52 GMT

Запрос/пользователь/картинка/2218:

GET https://myserver/dev30281_WebServices/api/user/picture/2218 HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.2; WOW64; Trident/6.0; .NET4.0E; .NET4.0C; .NET CLR 3.5.30729; .NET CLR 2.0.50727; .NET CLR 3.0.30729)
Connection: Keep-Alive

И затем веб-браузер запрашивает учетные данные. Если я введу их, я получаю этот ответ:

HTTP/1.1 200 OK
Cache-Control: public, max-age=60
Content-Length: 3119
Content-Type: image/png
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 20 Dec 2013 14:50:17 GMT
4b9b3361

Ответ 1

Основная идея

Загрузите изображения с помощью JavaScript и покажите их на сайте. Преимущество заключается в том, что учетные данные аутентификации никогда не найдут своего пути в HTML. Они будут сопротивляться со стороны JavaScript.

Шаг 1: загрузка данных изображения через JS

Эта базовая функциональность AJAX (см. также XMLHttpRequest::open(method, uri, async, user, pw)):

var xhr = new XMLHttpRequest();
xhr.open("GET", "your-server-path-to-image", true, "username", "password");

xhr.onload = function(evt) {
  if (this.status == 200) {
    // ...
  }
};

Шаг 2: отформатируйте данные

Теперь, как мы можем отображать данные изображения? При использовании HTML обычно назначается URI атрибуту src элемента изображения. Мы можем применить тот же принцип здесь, за исключением того, что вместо http(s):// производных мы используем URI данных вместо http(s)://.

xhr.onload = function(evt) {
  if (this.status == 200) {
    var b64 = utf8_to_b64(this.responseText);
    var dataUri = 'data:image/png;base64,' + b64; // Assuming a PNG image

    myImgElement.src = dataUri;
  }
};

// From MDN:
// https://developer.mozilla.org/en-US/docs/Web/API/window.btoa
function utf8_to_b64( str ) {
    return window.btoa(unescape(encodeURIComponent( str )));
}

Canvas

Существует еще одна опция, которая заключается в рисовании загруженных данных в поле <canvas>. Таким образом, пользователь не сможет щелкнуть правой кнопкой мыши изображение (область, где располагается холст), в отличие от <img> и URI данных, где пользователь увидит длинный URI данных при просмотре панели свойств изображения.

Ответ 2

Загружаемый загрузчик Google создается с помощью angular js. Аналогичные проблемы возникли у его авторов. Значки размещались в другом домене и помещали их как img src= нарушали CSP. Итак, как и вы, они должны были получать изображения значков с помощью XHR, а затем каким-то образом удалять их в теги img.

Они описывают, как они его решили. После извлечения изображения с помощью XHR они записывают его в локальную файловую систему HTML5. Они помещают свой URL в локальную файловую систему в атрибут img src, используя директиву ng-src.

$http.get(doc.icon, {responseType: 'blob'}).success(function(blob) {
  console.log('Fetched icon via XHR');
  blob.name = doc.iconFilename; // Add icon filename to blob.
  writeFile(blob); // Write is async, but that ok.
  doc.icon = window.URL.createObjectURL(blob);
  ...
}

Что касается причины, я не знаю. Я предполагаю, что создание токена сеанса для извлечения изображений не может быть и речи? Я ожидаю, что заголовки Cookie будут отправлены? Это запрос с перекрестным происхождением? В этом случае вы устанавливаете свойство withCredentials? Возможно ли это P3P?

Ответ 3

Другим подходом было бы добавление конечной точки к вашим сайтам, которые проксировали запрос изображения. Таким образом, ваша страница может запрашивать его без учетных данных, а на задней панели будет выполняться проверка подлинности. Задний конец может также кэшировать изображение, если он не менялся часто, или вы знали, с какой частотой он был обновлен. Это довольно легко сделать на задней панели, делает ваш интерфейс простым и предотвращает отправку учетных данных в браузер.

Если проблема заключается в аутентификации, ссылки могут содержать одноразовый токен, сгенерированный для пользователя, который аутентифицирован и доступен только из своего текущего сеанса браузера. Предоставление безопасного доступа к контенту только для пользователя, для которого он предназначался, и только для того времени, когда им разрешен доступ к нему. Однако это также потребует работы на заднем плане.

Ответ 4

Мне кажется, что для решения вашей проблемы вы должны изменить дизайн своего приложения, а не пытаться взломать способы работы браузеров.

Запрос на безопасный URL-адрес всегда будет нуждаться в проверке подлинности, если он выполняется браузером с тегом img или в javascript.

Если вы можете автоматически выполнять авторизацию без взаимодействия с пользователем, вы можете сделать это на стороне сервера, и вам не нужно посылать клиенту + пароль для этого. Если это так, вы можете изменить код, расположенный за https://myserver/dev30281_WebServices/api/user/picture/2218, для выполнения авторизации и обслуживания изображения без HTTP-аутентификации, только если пользователь имеет право запросить его, в противном случае вернуть 403 запрещенный ответ (http://en.wikipedia.org/wiki/HTTP_403).

Другим возможным решением будет выделение страниц, содержащих защищенные изображения из остальной части приложения. Таким образом, теоретически у вас будет два одностраничных приложения. Пользователь должен будет войти в систему, чтобы получить доступ к защищенной части. Я не уверен, хотя, если это возможно в вашем случае, так как вы не указали все требования. Но имеет смысл, что если вы хотите обслуживать защищенные ресурсы, требующие проверки подлинности, пользователю следует запрашивать учетные данные, как это делает браузер.

Ответ 5

Я всегда разбираю

Значение заголовка Set-Cookie в предыдущем (или первом запросе на вход), а затем отправьте его в следующих запросах.

Что-то вроде этого

Ответ после первого запроса:

Date:Thu, 26 Dec 2013 16:20:53 GMT
Expires:-1
Pragma:no-cache
Set-Cookie:ASP.NET_SessionId=lb1nbxeyfhl5suii2hfchxpx; domain=.example.com; path=/; secure; HttpOnly
Vary:Accept-Encoding
X-Cdn:Served-By-Akamai
X-Powered-By:ASP.NET

Любой следующий запрос:

Accept:text/html,application/xhtml+xml
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8,ru;q=0.6
Cache-Control:no-cache
Connection:keep-alive
Cookie:ASP.NET_SessionId=lb1nbxeyfhl5suii2hfchxpx;

Как вы можете видеть, я отправляю значение ASP.NET_SessionId = "любое значение" в заголовке Cookie. Если сервер использует php, вы должны разобрать PHPSESSID = "некоторое значение"

Ответ 6

Вам нужно попробовать использовать заголовок Access-Control-Allow-Credentials: true. Однажды я столкнулся с проблемой с IE, которая в конечном итоге сводилась к использованию этого заголовка. Также установите $httpProvider.defaults.headers.get = { 'withCredentials' : 'true' } в код angular js.

Ответ 7

Что касается причины: я попробовал Chrome и Firefox, и оба помнят базовую авторизацию, только если учетные данные вводятся непосредственно из пользовательского интерфейса браузера, то есть всплывающего окна, созданного браузером. Он не запомнит, если учетные данные получены из JavaScript, хотя HTTP-запрос такой же. Я предполагаю, что это сделано по замыслу, но я не вижу, чтобы это упоминалось в стандарте.