如何防止浏览器调用基本的验证弹出窗口并使用Jquery处理401错误

How to prevent browser to invoke basic auth popup and handle 401 error using Jquery?

本文关键字:窗口 Jquery 错误 处理 调用 浏览器 何防止 验证      更新时间:2023-09-26

我需要使用基本身份验证发送授权请求。我已经使用jquery成功地实现了这一点。然而,当我收到401错误时,基本的auth浏览器弹出窗口被打开,jquery ajax错误回调没有被调用。

我最近也遇到了这个问题。由于在401基本摘要身份验证)的情况下,您无法更改浏览器显示弹出窗口的默认行为,因此有两种方法可以解决此问题:

  • 将服务器响应更改为不返回401。返回一个200代码,并在jQuery客户端中进行处理
  • 将用于授权的方法更改为标头中的自定义值。浏览器将显示基本摘要的弹出窗口。您必须在客户端和服务器上都对此进行更改。

    headers : {
      "Authorization" : "BasicCustom"
    }
    

还请看一下这个例子,以获得将jQuery与Basic Auth一起使用的例子。

返回一个通用的400状态代码,然后处理该客户端。

或者,您可以保留401,而不返回WWW-Authenticate标头,这正是浏览器通过身份验证弹出窗口所响应的。如果WWW-Authenticate标头丢失,则浏览器不会提示输入凭据。

正如其他人所指出的,更改浏览器行为的唯一方法是确保响应不包含401状态代码,或者如果包含,则不包含WWW-Authenticate: Basic标头。由于更改状态代码不是很语义化,也不可取,因此一个好的方法是删除WWW-Authenticate标头。如果您不能或不想修改web服务器应用程序,您可以始终通过Apache为其提供服务或代理(如果您还没有使用Apache)。

以下是Apache重写响应以删除WWW-Authenticate标头的配置。请求包含的IFF包含标头X-Requested-With: XMLHttpRequest(默认情况下由JQuery/AngularJS等主要Javascript框架设置),响应包含标头WWW-Authenticate: Basic

在Apache 2.4上进行了测试(不确定它是否能与2.2配合使用)。这取决于正在安装的mod_headers模块。(在Debian/Ubuuntu上,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>   

您可以使用如下所示的请求url来抑制基本身份验证弹出窗口:

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

如果您收到401错误(用户名或密码错误),jquery错误回调将正确处理。它可能会导致一些安全问题(在http协议而不是https的情况下),但它是有效的。

UPD:此解决方案支持将在Chrome 59 中删除

在请求标头中使用X-Requested-With:XMLHttpRequest。因此,响应标头将不包含WWW-Authenticate:Basic。

beforeSend: function (xhr) {
                    xhr.setRequestHeader('Authorization', ("Basic "
                        .concat(btoa(key))));
                    xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
                },

如果使用IIS服务器,可以设置IIS URL重写(v2),将请求的URL上的WWW-Authentication标头重写为None

在这里指导。

要更改的值为response_www_authenticate

如果你需要更多信息,请添加评论,我会发布web.config文件。

如果WWW-Authenticate标头被删除,那么您将无法缓存凭据,也无法在请求中返回Authorization标头。这意味着现在您必须为生成的每个新请求输入凭据。

我还没有探究修复的原因或范围,但我发现如果我正在执行fetch请求,并添加标题x-requested-with: 'XMLHttpRequest',我将不再在Chrome中获得弹出验证框,也不需要更改服务器。它正在与节点http库对话。看起来WWW-Authenticate标头是从服务器返回的,但Chrome对它的处理方式不同。可能是规格。

示例:

fetch(url, {
  headers: {
     Authorization: `Basic ${auth}`,
     'x-requested-with': 'XMLHttpRequest'
  },
  credentials: 'include'
})

或者,如果您可以自定义服务器响应,则可以返回403 Forbidden。

浏览器将不会打开身份验证弹出窗口,并且将调用jquery回调。

在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');

创建/login-url,然后通过GET接受"用户"answers"密码"参数,并且不需要基本的身份验证。在这里,使用php、node、java等,解析您的passwd文件并根据它匹配参数(user/pass)http://user:pass@domain.com/(这将在您的浏览器上设置凭据)如果没有,则发送401响应(无WWW-Authenticate标头)。

在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);
            }
        }
    };
}