使用 AngularJS 拦截器阻止 HTTP 基本身份验证对话框

Posted

技术标签:

【中文标题】使用 AngularJS 拦截器阻止 HTTP 基本身份验证对话框【英文标题】:Preventing HTTP Basic Auth Dialog using AngularJS Interceptors 【发布时间】:2014-07-30 14:02:59 【问题描述】:

我正在使用 RESTful API 构建 AngularJS (1.2.16) Web 应用程序,并且我想针对身份验证信息无效或不存在的请求发送 401 Unauthorized 响应。当我这样做时,即使存在 HTTP 拦截器,当通过 AngularJS 发出 AJAX 请求时,我也会看到浏览器显示的基本“需要身份验证”对话框。我的拦截器在该对话框之后运行,这为时已晚,无法做一些有用的事情。

一个具体的例子:

除非存在授权令牌,否则我的后端 API 会为 /api/things 返回 401。漂亮又简单。

在 AngularJS 应用程序方面,我查看了 docs 并在 config 块中设置了这样的拦截器:

$httpProvider.interceptors.push(['$q', function ($q) 
  return 
    'responseError': function (rejection) 
      if (rejection.status === 401) 
        console.log('Got a 401')
      
      return $q.reject(rejection)
    
  
])

当我加载我的应用程序,删除身份验证令牌,并对 /api/things 执行 AJAX 调用(希望触发上述拦截器)时,我看到了:

如果我取消该对话框,我会看到“Got a 401”的console.log 输出,这是我希望看到的而不是那个对话框

很明显,拦截器在工作,但是拦截太晚了!

我在网上看到很多关于在这种情况下使用 AngularJS 进行身份验证的帖子,它们似乎都使用了 HTTP 拦截器,但没有一个提到弹出的基本身份验证对话框。我对其外观的一些错误想法包括:

响应中缺少Content-Type: application/json 标头?不,它就在那里。 需要返回承诺拒绝以外的其他内容?无论返回什么,该代码始终在对话框之后运行。

我是否遗漏了一些设置步骤或错误地使用了拦截器?

【问题讨论】:

为什么不使用 403 而不是 401? 我使用了 401,因为在最初的问题中,我删除了身份验证令牌(在我的情况下是 cookie)。 401 是在这里使用的正确响应代码,因为该请求需要身份验证并且没有提供。 403 用于提供身份验证但服务器拒绝访问资源时。 【参考方案1】:

想通了!

诀窍是发送一个WWW-Authenticate 响应标头,它的值不是Basic。然后,您可以使用基本的 $http 拦截器或更聪明的东西(例如 angular-http-auth)来捕获 401。

【讨论】:

您能详细说明一下吗? “像 angular-http-auth 这样更聪明的东西”要求服务器发送 200 响应。我正在寻找服务器返回 401 代码的解决方案。 我认为它只在'Authorization'时弹出:'Basic XXX' 请发布一些代码示例,我遇到了同样的问题。 ios 基本身份验证请求永远不会完成 有没有例子可以使用这个angular-http-auth并拦截响应来修改请求头?【参考方案2】:

我在 Spring Boot Security (HTTP basic) 中遇到了这个问题,从 Angular 1.3 开始,您必须设置 $httpProvider.defaults.headers.common["X-Requested-With"] = 'XMLHttpRequest'; 才能不出现弹出窗口。

【讨论】:

它不适用于我使用 Angular 1.5 和 firefox 48.x 的应用程序...即使使用 X-Requested-With 标头也会触发基本身份验证提示... 对我来说也一样。我已将此标头添加到邮递员中。但它不起作用。 这解决了我的问题。应用您的问题后,浏览器身份验证模式被隐藏。谢谢。 这个想法对我有用,虽然我创建了一个拦截器,并且仅在请求 URL 用于我的 REST API 时才添加此标头。【参考方案3】:

供将来参考

我在尝试处理401 错误时提出了这个解决方案。 我没有选项将Basic 重写为x-Basic 或类似的东西,所以我决定在客户端使用 Angular 来处理它。

启动注销时,首先尝试向假用户发出错误请求以丢弃当前缓存的凭据。

我有这个函数来处理请求(它使用 jquery 的 $.ajax 并禁用异步调用):

function authenticateUser(username, hash) 
    var result = false;
    var encoded = btoa(username + ':' + hash);

    $.ajax(
        type: "POST",
        beforeSend: function (request) 
            request.setRequestHeader("Authorization", 'Basic ' + encoded);
        ,
        url: "user/current",
        statusCode: 
            401: function () 
                result = false;
            ,
            200: function (response) 
                result = response;
            
        ,
        async: false
    );

    return result;

所以当我尝试注销用户时,会发生这种情况:

//This will send a request with a non-existant user.
//The purpose is to overwrite the cached data with something else
accountServices.authenticateUser('logout','logout');

//Since setting headers.common.Authorization = '' will still send some
//kind of auth data, I've redefined the headers.common object to get
//rid of the Authorization property
$http.defaults.headers.common = Accept: "application/json, text/plain, */*";

【讨论】:

以上是关于使用 AngularJS 拦截器阻止 HTTP 基本身份验证对话框的主要内容,如果未能解决你的问题,请参考以下文章

angularjs拦截器:为啥在$http拦截器中没有收到正确的状态码和消息

AngularJs HTTP响应拦截器实现登陆权限校验

如何在 AngularJS 中插入命令或阻止 $http 的 JSONP 自动解析?

AngularJS的弹性搜索——跨域请求被阻止

关于angularJs的拦截器的使用

跨域请求被阻止 Symfony/AngularJS