在vue中处理axios请求返回的认证页面

Handling an authentication page returned by an axios request in vue

我有一个位于防火墙后面的 vue 应用程序,它控制身份验证。当您第一次访问该应用程序时,您需要进行身份验证,然后您才能访问该应用程序,一切正常,直到身份验证过期。从我的应用程序的角度来看,我只知道当我使用 axios 发送 API 请求时用户需要重新进行身份验证,而不是预期的有效负载,我收到了 403 错误,我发现了一些错误像下面这样:

import axios  from 'axios'
var api_url = '...'

export default new class APICall {
    constructor() {
        this.axios = axios.create({
            headers: {
                'Accept': 'application/json',
                'Cache-Control': 'no-cache',
                'Content-Type': 'application/json',
            },
            withCredentials: true,
            baseURL: api_url
        });
    }
    // send a get request to the API with the attached data
    GET(command) {
        return this.axios.get(command)
            .then((response) => {
                if (response && response.status === 200) {
                    return response.data; // all good
                } else {
                    return response;      // should never happen
                }
            }).catch((err) => {
                if (err.message
                    && err.message=="Request failed with status code 403"
                    && err.response && err.response.data) {
                    // err.response.data now contains HTML for the authentication page
                    // and successful authentication on this page resends the
                    // original axios request, which is in err.response.config
                }
            })
    }
}

在 catch 语句中,err.response.data 是身份验证页面的 HTML 并且在此页面上成功进行身份验证会自动重新触发原始请求,但我这辈子都看不到如何将其用于 return 我想要应用程序的有效载荷。

虽然从安全的角度来看并不理想,但我可以在执行此操作时使用 v-html 标记显示 err.response.data 的内容 我无法弄清楚如何捕获返回的有效负载当身份验证页面触发原始请求时,有效负载最终会显示在浏览器中。有谁知道如何做到这一点?我已经尝试将所有内容包装在 promises 中,但我认为问题在于我没有对重新触发的请求做出承诺,因为我无法直接控制它。

我是否需要修改 err.response.data 中的表单来控制数据的 return 编辑方式?我觉得我应该使用拦截器,但不完全确定它们是如何工作的...

编辑

我意识到最干净的方法是在新的 window 中打开 error.response.data 中的表单,以便用户可以重新验证,使用类似的东西:

var login_window = window.open('about:blank', '_blank');
login_window.document.write(error.response.data)

重新验证成功后,login_window 现在包含原始 axios get 请求的 json。所以我的问题现在变成了如何检测身份验证何时触发并且 login_window 包含我想要的 json 。如 Detect form submission on a page 中所述,从格式 window 中提取 json 也是有问题的,因为当我查看 login_window.document.body.innerText "by hand" 时,我看到一个文本字符串的形式

JSON
Raw Data
Headers
Save
Copy
Collapse All
Expand All

status  \"OK\"
message \"\"
user    \"andrew\"

但如果有一种可靠的方法来确定用户何时在页面上提交登录表单,我会很高兴 login_window,然后我可以重新发送请求。

一种解决方案是覆盖 <form> 的提交事件处理程序,然后使用 Axios 提交表单,这样您就可以访问表单的响应数据。

步骤:

  1. 查询 <form> 元素的表单容器:

    // <div ref="container" v-html="formHtml">
    const form = this.$refs.container.querySelector('form')
    
  2. 添加一个 submit 事件处理程序调用 Event.preventDefault() 来停止提交:

    form.addEventListener('submit', e => {
      e.preventDefault()
    })
    
  3. 使用 Axios 发送原始请求,添加您自己的响应处理程序以获取结果数据:

    form.addEventListener('submit', e => {
      e.preventDefault()
    
      axios({
        method: form.method,
        url: form.action,
        data: new FormData(form)
      })
      .then(response => {
        const { data } = response
        // data now contains the response of your original request before authentication
      })
    })
    

demo

我会采取不同的方法,这取决于您对 API:

的控制
  • 选项 1:您可以控制(或包装)API
    1. 具有 API return 401(未授权 - 意味着需要验证)而不是 403(禁止 - 意味着没有适当的访问权限)
    2. 创建身份验证 REST API(例如 POST https://apiserver/auth),其中 return 是一个新的身份验证令牌
    3. 使用 Axios 拦截器:
this.axios.interceptors.response.use(function onResponse(response) {
    // Any status code that lie within the range of 2xx cause this function to trigger
    // no need to do anything here
    return response;
  }, async function onResponseError(error) {
    // Any status codes that falls outside the range of 2xx cause this function to trigger
    if ("response" in error && "config" in error) { // this is an axios error
      if (error.response.status !== 401) { // can't handle
        return error;
      }
      this.token = await this.axios.post("auth", credentials);
      error.config.headers.authorization = `Bearer ${this.token}`;
      return this.axios.request(config);
    }
    return error; // not an axios error, can't handler
  });

这样做的结果是用户根本没有体验到这一点,一切照常进行。

  • 选项 2:您无法控制(或包装)API
    1. 使用拦截器:
this.axios.interceptors.response.use(function onResponse(response) {
    // Any status code that lie within the range of 2xx cause this function to trigger
    // no need to do anything here
    return response;
  }, async function onResponseError(error) {
    // Any status codes that falls outside the range of 2xx cause this function to trigger
    if ("response" in error && "config" in error) { // this is an axios error
      if (error.response.status !== 403) { // can't handle
        return error;
      }
      if (!verifyLoginHtml(error.response.data)) { // this is not a known login page
        return error;
      }
      const res = await this.axios.post(loginUrl, loginFormData);
      return res.data; // this should be the response to the original request (as mentioned above)
    }
    return error; // not an axios error, can't handler
  });