XHR 之后如何在浏览器缓存中存储基本身份验证凭据

ElJ*_*ste 6 javascript ajax caching nginx basic-authentication

语境

我有一个朋友,他的名字叫鲍勃。

Bob 有一个服务器,其中的应用程序正在运行并且只能在本地访问。为了从外部世界访问此应用程序,Bob使用 nginx 和模块auth_basic安装并配置了反向代理

每个请求都经过反向代理的身份验证过程。两种情况:

  1. 如果HTTP GET 请求包含有效的 HTTP 标头参数Authorization: Basic base64credentials,则反向代理将访问本地应用程序并相应地响应。每个子请求都不需要新的身份验证,因为浏览器会缓存凭据并在每个请求中自动发送它们,直到我们关闭浏览器。
  2. 如果HTTP GET 请求不包含有效的 HTTP 标头参数,反向代理将直接使用 HTTP 标头WWW-Authenticate: Basic Realm="User Visible Realm"进行响应。然后浏览器会自动显示一个对话框以输入凭据。

直到这里一切都正常。这就像基本身份验证规范所期望的那样。

问题

Bob 不喜欢浏览器的默认对话框,并且想要一个带有表单的漂亮 html 页面。他将 nginx 服务器配置为拥有自己的 html 表单。

问题在于,默认情况下,表单的 HTML 提交过程不会发送Authorization标头参数。Bob 需要使用XMLHttpRequest。他实现了它,并在凭据良好时从服务器收到良好的200 HTTP 响应。

与默认表单行为不同,使用 XMLHttpRequest 时,浏览器不会在成功时自动缓存凭据。因此每个子请求都会再次显示登录表单:'(

Bob 无法更改本地应用程序的前端代码以自行发送每个请求中的凭据(与 SPA 一样)。事实上,他无权访问该应用程序。他只能访问 nginx conf 和他自己的 html 登录表单。所以存储在这里是没有用的。

问题

bob 是否可以在收到 XHR 响应后让浏览器缓存凭据?

(目标是即使使用 XMLHttpRequest 也能表现得像默认行为)


编辑 :

进一步说明

在此输入图像描述

本地应用程序正在本地主机上运行。鲍勃没有开发这个应用程序,他无法编辑它。这个应用程序不提供身份验证,Bob 使用 nginx 的 basic_auth 模块作为反向代理来对人员进行身份验证。

它运行良好,但使用实现基本身份验证规范的浏览器的默认行为。此行为会显示丑陋的表单并在成功时缓存凭据。如果 Bob 提供他自己的表单,则行为继续,这是正常的,因为基本身份验证规范需要HTML 表单无法提供的特定标头参数(授权:基本...) 。Bob需要使用XHR来提供这个参数。

问题是,如何使用 XHR 恢复浏览器的良好行为?

我们只能在login.html上使用JS,而不能在本地应用程序上使用JS。这是工作流程:

  1. 向服务器发出 HTTP GET 请求
  2. 服务器找不到授权参数或凭据错误
  3. 服务器响应login.html
  4. 用户通过表单提供凭据。XHR 与授权参数一起发出。
  5. 服务器发现授权参数和凭据有效
  6. 服务器返回本地应用程序入口文件(例如index.html)
  7. 浏览器读取index.html并想要请求其他文件(img、css、js...)
  8. 这些子请求将失败,因为这些请求中没有提供凭据。
  9. 如果使用丑陋的默认表单,凭据会自动缓存并且可以正常工作。

我还精确地指出,解决方案是用真正的后端应用程序和另一个身份验证系统(例如自动发送的 cookie)替换 nginx 基本身份验证反向代理,该系统将作为反向代理工作,但这不是提出的问题。


编辑2:

为什么 Bob 不能使用存储解决方案?

在 ulgy 表单场景中,他没有 HTML 登录文件。当浏览器客户端向服务器发出请求时,服务器仅响应带有WWW-Authenticate标头但不包含 HTML 内容的 HTTP 响应。让这个标题参数显示一个表单的简单事实。只需输入正确的凭据就会发回 200 HTTP 响应,浏览器将缓存凭据并在每个带有 HTTP 标头Authorization: Basic的请求中发送它。

在 login.html 场景中,成功登录后,我们需要在每个请求中发回 HTTP 标头Authorization: Basic不是 cookie,因为这是 Basic Auth 规范的工作方式,而 Bob 没有任何后端,只有 nginx模块)。可以从 login.html 发送此标头,因为我们可以在其上附加 JS。但是,服务器响应的下一个页面将是来自本地应用程序的 HTML 文件,Bob 无权访问其 HTML,也无法在其上附加 JS 来为下一个请求提供标头Authorization: Basic。可以从 login.html 文件中存储 cookie,但需要从其他页面检索此 cookie 并用于发送标头Authorization: Basic,这是不可能的,因为 Bob 无权访问这些页面的 JS。

先感谢您。

cow*_*ert 0

由于您已经在使用 ajax,因此只需设置 javascript 并读取 cookie:

(我在这里使用 jQuery,为了简单起见,如果您不使用 jQuery,请用适当的语法替换 ajax 调用):

function getCookie(cookiename) {
    /* a function to find a cookie based on its name */

    var r = document.cookie.match('\\b' + cookiename + "=([^;]*)\\b");
    // document.cookie returns all cookies for this url
    return r ? r[1] : undefined;
    // return the regex capture if it has content, otherwise return undefined
}

function getData(auth_basic) {
    $.ajax({
        url: 'url_of_nginx...',
        headers: {
            'Authorization': 'Basic ' + auth_basic
        } // send auth header on xmlhttprequest GET
    }).next(function ajaxSuccess(data) {
        // data from the nginx
        document.cookie = '_auth_cookie=' + auth_basic;
        // store my auth in a cookie
    }, function ajaxFailed(jqXHR) {
        // do something on failure, like
        showLoginForm()
    });
}

function showLoginForm() {
    /* function to render your form */

    // attach an event handler to form submission
    $('#submit_button_id').click(function form_submitted(login_evt) {
        // I clicked login
        login_evt.preventDefault(); // don't really submit the form

        // get my field form values
        username = $('#username_input_field').val();
        password = $('#password_input_field').val();

        // I base64 the auth string
        var auth_basic = btoa(username + ':' + password);

        // try to auth
        getData(auth_basic);

    });
}

var auth_cookie = getCookie('_auth_cookie');
if (auth_cookie === undefined) {
    // I have no cookie
    showLoginForm()       
} else {
    getData(auth_cookie)
}
Run Code Online (Sandbox Code Playgroud)