Как запретить браузеру вызывать базовое всплывающее окно аутентификации и обрабатывать ошибку 401 с помощью JQuery?

107

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

Алексей Захаров
источник

Ответы:

46

Я тоже недавно столкнулся с этой проблемой. Поскольку вы не можете изменить поведение браузера по умолчанию, отображающее всплывающее окно в случае 401( базовой или дайджест- аутентификации), есть два способа исправить это:

  • Измените ответ сервера, чтобы он не возвращал 401. 200Вместо этого верните код и обработайте его в своем клиенте jQuery.
  • Измените метод, который вы используете для авторизации, на произвольное значение в заголовке. Браузеры будут отображать всплывающее окно для Basic и Digest . Вы должны изменить это как на клиенте, так и на сервере.

    headers : {
      "Authorization" : "BasicCustom"
    }

Также взгляните на это, чтобы увидеть пример использования jQuery с Basic Auth.

нвинклер
источник
10
WWW-Authenticate: xBasic realm = com.example может это сделать вместе с классическим кодом состояния 401. это сообщение в блоге показало мне подсказку (я не являюсь владельцем блога) loovchar.blogspot.ca/2010/11/…
PM
2
@PM, ответ блога - отличное решение. Обратите внимание, что при использовании <security:http-basic/>вам не нужно определять, basicAuthenticationFilterно следует определить его как <security:http-basic entry-point-ref="myBasicAuthenticationEntryPoint"/>.
Brett Ryan
Не могли бы вы рассказать мне, как переопределить ответ перед отправкой обратно клиенту, я использую jaxrs с базовой аутентификацией. какой класс мне нужно переопределить для изменения ответа?
Мохаммед Самин
По какой-то причине я получаю всплывающее окно, когда возвращаю a, 401и WWW-Authenticate:Bearer WWW-Authenticate:NTLM WWW-Authenticate:Negotiateзнаете ли вы, почему это должно быть
DevEng
34

Верните общий код состояния 400, а затем обработайте его на стороне клиента.

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

Ибрагим
источник
6
@MortenHaraldsen Что ж, ответ 401 является правильным ответом в этом случае, проблема в том, что браузер автоматически обрабатывает это изначально, вместо того, чтобы разрешать приложению javascript обрабатывать его. Вы можете придерживаться стандарта, не возвращая правильный ответ, рекомендованный стандартом, или можете отказаться от него, вернув код ответа, рекомендованный стандартом. Выбирайте :)
Ибрагим
В моем экспресс-приложении я исправил это одной строкой: res.removeHeader('www-authenticate'); // prevents browser from popping up a basic auth window.
gstroup
1
@Ibraheem, я не мог бы сказать лучше. Стандарты создаются людьми, которые садятся и разговаривают, не обязательно теми, кто садится и кодирует.
user2867288 01
15

Вы можете подавить базовое всплывающее окно аутентификации, указав URL-адрес запроса следующим образом:

https://username:password@example.com/admin/...

Если вы получите ошибку 401 (неправильное имя пользователя или пароль), она будет правильно обработана с помощью обратного вызова ошибки jquery. Это может вызвать некоторые проблемы с безопасностью (в случае использования протокола http вместо https), но это работает.

UPD: поддержка этого решения будет удалена в Chrome 59

Иван Самовар
источник
УДИВИТЕЛЬНЫЙ!!!! Решил мою проблему, так как проблема заключалась в попытке 192.168.1.1, а маршрутизатор продолжал запрашивать аутентификацию.
Надав Лебович
Если вы перейдете на вкладку «Сеть» в инструментах разработчика в любом браузере, вы сможете прочитать имя пользователя и пароль в виде обычного текста. Но это работает.
Бруно Фингер,
19
Никогда не делайте этого, пожалуйста, журналы запросов на вашем веб-сервере сейчас для меня гораздо более ценны .. Бесплатные комбинации имен пользователей и паролей плюс код ответа! Спасибо
Remco
но как можно предотвратить просмотр имени пользователя и пароля
sdx11
3
Этот метод аутентификации становится устаревшим, и Chrome прекращает поддержку встроенных учетных данных, например, https://user:pass@host/в M59 примерно в июне 2017 года. Дополнительную информацию см. В этом сообщении блога chromestatus .
Garywoo
13

Как указывали другие, единственный способ изменить поведение браузера - убедиться, что ответ либо не содержит код состояния 401, либо, если он есть, не включает WWW-Authenticate: Basicзаголовок. Поскольку изменение кода состояния не очень семантично и нежелательно, хорошим подходом является удалениеWWW-Authenticate заголовок. Если вы не можете или не хотите изменять приложение веб-сервера, вы всегда можете обслуживать или проксировать его через Apache (если вы еще не используете Apache).

Вот конфигурация для Apache, чтобы переписать ответ для удаления заголовка WWW-Authenticate. IFF, содержащий запрос, содержит заголовок X-Requested-With: XMLHttpRequest(который установлен по умолчанию основными фреймворками Javascript, такими как JQuery / AngularJS и т. Д.) И ответ содержит заголовок WWW-Authenticate: Basic.

Проверено на Apache 2.4 (не уверен, что он работает с 2.2). Это зависит от устанавливаемого mod_headersмодуля. (В Debian / Ubuntu sudo a2enmod headersи перезапустите Apache)

    <Location />
            # Make sure that if it is an XHR request,
            # we don't send back basic authentication header.
            # This is to prevent the browser from displaying a basic auth login dialog.
            Header unset WWW-Authenticate "expr=req('X-Requested-With') == 'XMLHttpRequest' && resp('WWW-Authenticate') =~ /^Basic/"
    </Location>   
Сястров
источник
1
Чтобы сделать то же самое с Nginx, установитеproxy_hide_header WWW-Authenticate;
Cuga
7

Используйте X-Requested-With: XMLHttpRequest с заголовком запроса. Таким образом, заголовок ответа не будет содержать WWW-Authenticate: Basic.

beforeSend: function (xhr) {
                    xhr.setRequestHeader('Authorization', ("Basic "
                        .concat(btoa(key))));
                    xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
                },
Седху
источник
1
На меня это не подействовало. Какой тип сервера вы используете, что WWW-аутентификация не отправляется, когда вы устанавливаете XMLHttpRequest?
Роберт Антонуччи
@RobertAntonucci, это apache tomcat
sedhu
5

Если вы используете сервер IIS, вы можете настроить перезапись URL-адреса IIS (v2), чтобы переписать WWW-Authenticationзаголовок Noneна запрошенный URL-адрес.

Гид здесь .

Значение, которое вы хотите изменить, - response_www_authenticate.

Если вам нужна дополнительная информация, добавьте комментарий, и я отправлю файл web.config.

Зимотик
источник
1
Это отлично сработало. Я хотел бы отметить, что часть «ответ» должна быть записана как «RESPONSE_www_authenticate» в URL Rewrite v2 на IIS 7.5.
Майкл Фриман,
3

Если заголовок WWW-Authenticate удален, вы не получите кеширование учетных данных и не получите обратно заголовок авторизации в запросе. Это означает, что теперь вам нужно будет вводить учетные данные для каждого нового запроса, который вы создаете.

user2491441
источник
Это очень важно, абсолютно точно.
Tez Wingfield
2

В качестве альтернативы, если вы можете настроить ответ сервера, вы можете вернуть 403 Forbidden.

Браузер не откроет всплывающее окно аутентификации, и будет вызван обратный вызов jquery.

Хавьер Ферреро
источник
5
Это противоречит спецификации HTTP 1.1, где сказано, что «... Авторизация не поможет и запрос НЕ ДОЛЖЕН повторяться».
Юкка Дальбом
1
Допустимо получение 403 во время аутентификации для ресурсов, к которым вам не разрешен доступ, 401 следует отправлять туда, где вы еще не прошли аутентификацию.
Brett Ryan
1

В Safari вы можете использовать синхронные запросы, чтобы браузер не отображал всплывающее окно. Конечно, синхронные запросы следует использовать только в этом случае для проверки учетных данных пользователя ... Вы можете использовать такой запрос перед отправкой фактического запроса, который может вызвать неудобства для пользователя, если контент (отправленный или полученный) довольно тяжелый.

    var xmlhttp=new XMLHttpRequest;
    xmlhttp.withCredentials=true;
    xmlhttp.open("POST",<YOUR UR>,false,username,password);
    xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
    xmlhttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
Эммануэль Селье
источник
В других контекстах также может помочь использование OPTIONS вместо POST.
Эммануэль Селье
0

Создайте URL-адрес / login, а не принимайте параметры «пользователь» и «пароль» через GET и не требуйте базовой аутентификации. Здесь используйте php, node, java, что угодно и проанализируйте свой файл passwd и сопоставьте параметры (пользователь / пароль) с ним. Если есть совпадение, перенаправьте на http: // user: pass@domain.com/ (это установит учетные данные в вашем браузере), если нет, отправьте ответ 401 (без заголовка WWW-Authenticate).

Neiker
источник
Это было бы супер-здорово для любого, кто пытается вынюхать имя пользователя / пароли из простого текстового человека в середине атаки!
Ajax
0

С обратной стороны с Spring Boot я использовал собственный BasicAuthenticationEntryPoint:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.cors().and().authorizeRequests()
            ...
            .antMatchers(PUBLIC_AUTH).permitAll()
            .and().httpBasic()
//    https://www.baeldung.com/spring-security-basic-authentication
            .authenticationEntryPoint(authBasicAuthenticationEntryPoint())
            ...

@Bean
public BasicAuthenticationEntryPoint authBasicAuthenticationEntryPoint() {
    return new BasicAuthenticationEntryPoint() {
        {
            setRealmName("pirsApp");
        }

        @Override
        public void commence
                (HttpServletRequest request, HttpServletResponse response, AuthenticationException authEx)
                throws IOException, ServletException {
            if (request.getRequestURI().equals(PUBLIC_AUTH)) {
                response.sendError(HttpStatus.PRECONDITION_FAILED.value(), "Wrong credentials");
            } else {
                super.commence(request, response, authEx);
            }
        }
    };
}
Григорий Кислин
источник