如何抑制浏览器的身份验证对话框?

93

我的网络应用有一个登录页面,可以通过AJAX调用提交验证凭据。如果用户输入了正确的用户名和密码,则一切正常,但如果不正确,则会出现以下情况:

  1. Web服务器确定尽管请求包含格式良好的Authorization头,但头中的凭据不能成功验证。
  2. Web服务器返回401状态代码,并包括一个或多个WWW-Authenticate头,列出支持的认证类型。
  3. 浏览器检测到XMLHttpRequest对象对我的调用的响应是401且响应包含WWW-Authenticate头,然后弹出身份验证对话框,再次要求输入用户名和密码。

这一切都很好,直到第3步。我不希望弹出对话框,我希望在我的AJAX回调函数中处理401响应。(例如,在登录页面上显示错误消息。)当然,我希望用户重新输入用户名和密码,但我希望他们看到我友好的,令人放心的登录表单,而不是浏览器丑陋的默认身份验证对话框。

顺便说一句,我无法控制服务器,因此让它返回自定义状态代码(即不是401)不是选择。

有没有办法可以抑制身份验证对话框?特别是,我能否抑制Firefox 2或更高版本中的身份验证要求对话框?有没有办法抑制IE 6及更高版本中的“连接到[主机]”对话框?


编辑
作者的附加信息(9月18日):
我应该补充一下,浏览器的身份验证对话框弹出的真正问题是它给用户提供了不足的信息。

用户刚刚通过登录页面上的表单输入了用户名和密码,他认为自己已经正确地输入了它们,并且点击了提交按钮或按下了回车键。他的期望是他将被带到下一个页面或者可能告诉他他输入了错误的信息,应该再试一次。然而,他却看到了一个意外的对话框。

这个对话框没有确认他刚刚输入了用户名和密码的事实。它没有清楚地表明存在问题,也没有提示用户再试一次。相反,这个对话框向用户呈现了晦涩难懂的信息,如“The site says: '[realm]'”。“[realm]”是一个只有程序员才会喜欢的短领域名。

Web浏览器设计者们请注意:如果对话框本身更加用户友好,那么没有人会问如何抑制认证对话框。我做登录表单的整个原因就在于我们的产品管理团队正确地认为浏览器的认证对话框非常糟糕。


1
答案是没有好的答案。Marijn建议的hack是最接近的方法。当然,如果可能的话,使用服务器和您的JavaScript都理解但浏览器不理解的自定义身份验证也可以达到目的。 - dgvid
我曾经遇到过同样的问题,并在stackoverflow的评论中找到了这个链接(不是我的博客):http://loudvchar.blogspot.ca/2010/11/avoiding-browser-popup-for-401.html 希望它能帮到你。 - gies0r
12个回答

57

我在这里遇到了同样的问题,而我公司的后端工程师实现了一种被认为是良好实践的行为:当对一个URL的调用返回401时,如果客户端设置了头X-Requested-With: XMLHttpRequest,服务器会在其响应中删除www-authenticate头。

副作用是默认的身份验证弹出窗口不会出现。

确保你的API调用设置了X-Requested-With头,并将其设置为XMLHttpRequest。如果是这样,除了根据此良好实践更改服务器行为外,无需进行任何操作...


5
如果后端是基于Java/Spring的,DelegatingAuthenticationEntryPoint 会为您处理此行为。 - Derek Slife
谢谢您。包括 Devise 在内的许多库都采用了这种行为。 - josephnvu
2
有没有任何想法如何在nginx中实现此功能? "当调用URL返回401时,如果客户端设置了标头X-Requested-With:XMLHttpRequest,则服务器会在其响应中删除www-authenticate标头。" - markmnl

22
当满足以下两个条件时,浏览器会弹出登录提示框:
  1. HTTP状态码为401
  2. 响应中存在WWW-Authenticate头部信息
如果您能控制HTTP响应,那么可以从响应中删除WWW-Authenticate头部信息,这样浏览器就不会弹出登录对话框。
如果无法控制响应,则可以设置代理以过滤响应中的WWW-Authenticate头部信息。
据我所知(如有错误请指正),一旦浏览器接收到WWW-Authenticate头部信息后,就无法防止登录提示框的出现。

好的信息。关于有效的 WWW-Authenticate 标头值,请参见 https://dev59.com/zHI-5IYBdhLWcg3wpaF1#1748451。 - Brice Roncace

17

我认为这是不可能的-如果您使用浏览器的HTTP客户端实现,它将始终弹出该对话框。我想到了两个方法:

  1. 也许Flash的处理方式不同(我还没有尝试过),因此让Flash电影进行请求可能会有所帮助。

  2. 您可以为自己访问的服务设置“代理”在您自己的服务器上,并稍微修改身份验证标头,以便浏览器无法识别它们。


3
@7SpecialGems 好发现。我不是在暗示这是一个重复的问题,我只是链接到了特定的答案(WWW-Authenticate那个),它是在被接受的答案发布4年后发布的。虽然回想起来,我不记得当时为什么要查看它,或者我如何测试它了。 - Stobor
1
我尝试使用以下代码捕获未授权的401错误:$.ajaxSetup({ statusCode: { 401: function () { RedirectToLogin(); } } });但是IE总是显示浏览器的身份验证对话框。我如何在ASP.Net MVC 2中禁止此对话框? - PaulP
@PaulP,就像其他人所说的那样,你需要一个代理来剥离401状态码响应的WWW-Authenticate头,或者一些自定义服务器实现。 - Motomotes
最终的方法在这里描述:https://dev59.com/a3NA5IYBdhLWcg3wgeSk#19102200 - 它是可能的,我用它来实现从失败的SPNEGO(Windows SSO / Kerberos)身份验证的透明回退。 - comeGetSome
2
@dgvid 对不起,但“不可能”这个说法让我不理性地生气。我知道计算机能做什么,而且绝对有可能实现,这让我对设计它的人感到非常愤怒,因为他们没有提供这种能力。 - Michael
显示剩余4条评论

9
我知道这个问题及其回答非常古老,但我还是来到了这里。或许其他人也会来到这里。
如果您可以访问返回401状态码的Web服务代码,请在这种情况下将服务更改为返回403(禁止)状态码。在收到403响应时,浏览器不会提示输入凭据。对于未经授权的已验证用户访问特定资源而言,403是正确的状态码,这似乎就是问题的所在。
关于403的IETF文档提到:
服务器收到有效但无法获得访问权限的凭证时,应该用403(禁止)状态码作出响应。

4
在Mozilla中,当您创建XMLHttpRequest对象时,可以使用以下脚本实现:
xmlHttp=new XMLHttpRequest();
xmlHttp.mozBackgroundRequest = true;
xmlHttp.open("GET",URL,true,USERNAME,PASSWORD);
xmlHttp.send(null);

第二行代码防止了对话框...

1
在Firefox 2下似乎没有任何作用。在Firefox 3下会导致DOM安全错误,错误代码为NS_ERROR_DOM_SECURITY_ERR 1000。 - dgvid

3
您使用哪种服务器技术,是否有特定的产品用于身份验证?
由于浏览器只是在执行其工作,因此我认为您必须在服务器端更改内容,以便不返回401状态代码。这可以通过使用自定义身份验证表单来完成,当身份验证失败时,简单地再次返回该表单。

2

如果您可以在服务器端更改401状态代码为另一个状态代码,那么浏览器就不会捕获并绘制弹出窗口。另一种解决方案可能是将WWW-Authenticate标头更改为另一个自定义标头。我不相信不同的浏览器不能支持它,在Firefox的几个版本中,我们可以使用mozBackgroundRequest进行xhr请求,但其他浏览器呢?在这里,有一个有趣的链接,其中讨论了这个问题在Chromium中的情况。


2
在Mozilla领域中,将XMLHttpRequest(文档)的mozBackgroundRequest参数设置为true可以抑制这些对话框并导致请求仅仅失败。然而,我不知道跨浏览器支持有多好(包括那些失败请求的错误信息质量是否在各个浏览器中都很好)。

moz- 前缀意味着没有跨浏览器支持(除非您可以找到在其他每个浏览器中都起作用的类似参数)。 - Brilliand

1
我在使用MVC 5和VPN时遇到了同样的问题,每当我们在VPN外部时,就会出现需要回答这个浏览器消息的情况。使用.net,我只需处理错误的路由即可。
<customErrors defaultRedirect="~/Error"  >
  <error statusCode="401" redirect="~/Index"/>
</customErrors>

到目前为止,它之所以有效,是因为主页控制器下的Index操作验证了用户。如果登录不成功,此操作中的视图具有我用于使用传递到Directory Services的LDAP查询来登录用户的登录控件。
      DirectoryEntry entry = new DirectoryEntry("LDAP://OurDomain");
      DirectorySearcher Dsearch = new DirectorySearcher(entry);
      Dsearch.Filter = "(SAMAccountName=" + UserID + ")";
      Dsearch.PropertiesToLoad.Add("cn");

虽然到目前为止这样做还不错,但我必须告诉你,我仍在测试它,上面的代码还没有运行的理由,因此可能会被删除...目前的测试包括尝试发现第二组代码更有用的情况。再次强调,这是一个正在进行中的工作,但由于它可能对您有所帮助或激发您的一些想法,我决定现在添加它...所有测试完成后,我将用最终结果更新它。

1
我最近在为三星Tizen智能电视开发Web应用程序时遇到了类似的情况。需要扫描完整的本地网络,但有些IP地址返回“401未经授权”的响应,并附有“www-authenticate”标题。这会弹出一个浏览器身份验证弹窗,要求用户输入“用户名”和“密码”,因为这是“Basic”身份验证类型(https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication)。
为了摆脱这个问题,对于Fetc Api Call (https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch),对我起作用的简单方法是设置credentials:'omit'。官方文档说:
相反,要确保浏览器不在请求中包括凭据,请使用credentials:'omit'

fetch('https://example.com', {
  credentials: 'omit'
})


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接