如何在axios和react中处理401(身份验证错误)?

95

我有一个文件request.js,其中包含axios ajax请求的封装程序。我从多个React组件中调用请求函数,当请求失败时,我想刷新令牌并重新尝试所有失败的请求。我可以使用拦截器,但我不知道如何实现它。请帮忙。

request.js

 var client = axios.create({
   baseURL: 'http://192.168.1.3:3000',
     headers: {
     appID: 8,
     version: "1.1.0",
     empID: localStorage.getItem('empID'),
     token: localStorage.getItem('accessToken')
    }
 });

 const request = function(options) {
     const onSuccess = function(response) {
         console.debug('Request Successful!', response);
         return response.data;
     } 
     const onError = function(error) {
         console.error('Request Failed:', error.config);
         if (error.response) {
             console.error('Status:',  error.response.status);
             console.error('Data:',    error.response.data);
             console.error('Headers:', error.response.headers);
         } else {
             console.error('Error Message:', error.message);
         }

         return Promise.reject(error.response || error.message);
     }

     return client(options)
         .then(onSuccess)
         .catch(onError);
         options
 }

 export default request;

我使用以下方法来捕获 401 错误: axios.post('/endpoint').then(...).catch(function (error) { console.log(error.response.status) //401 console.log(error.response.data.error) // Please Authenticate }) - Muhammad Shahzad
6个回答

139

如果你想使用拦截器来处理401错误,这是代码片段。

axios.interceptors.response.use(response => {
   return response;
}, error => {
  if (error.response.status === 401) {
   //place your reentry code
  }
  return error;
});

10
这是否改变了?对于401,响应始终未定义,在Axios中我收到了“网络错误”的提示。 - estani
@estani 你解决了吗?我也是,由于403错误,我得到了“网络错误”,Axios错误中没有包含“response”或“status”。 - LittleTiger
如果你在服务端没有得到除状态以外的任何响应,那么@LittleTiger这种方法就不会起作用。如果你想使用上述方法,请发送带有状态的响应。例如:res.status(401).send("message") 或 res.status(401).json({})。 - Pram
@TarasKryvko 是的,我在这里添加了一个答案,尽管问题可能是不同的。 - estani
3
我将把“return error”翻译为“返回错误”,然后将其改为“*return Promise.reject(error);*”。 - Ronald Coarite
显示剩余3条评论

21

这个有效:

// Add a 401 response interceptor
window.axios.interceptors.response.use(function (response) {
    return response;
}, function (error) {
    if (401 === error.response.status) {
        // handle error: inform user, go to login, etc
    } else {
        return Promise.reject(error);
    }
});

提取自: https://gist.github.com/yajra/5f5551649b20c8f668aec48549ef5c1f

我遇到了这个问题:

"Network Error" without any response

简而言之 - 这是CORS的问题,它的设置有误,因此axios无法从浏览器获取信息。您需要从服务器端解决这个问题。

描述

如果您遇到类似的问题,您将在浏览器控制台中看到它。浏览器将阻止您通过ajax访问不同的url。

在我的特定情况下(node.js- express),是过滤器的顺序,CORS过滤器(dev环境)是在处理此特定请求的处理程序之后添加的,因此服务器当时没有发送正确的CORS头,因此浏览器甚至不允许该请求发生(根本没有调用服务器,因此没有返回错误对象)。


感谢关于“网络错误但没有响应”的问题的说明。我遇到了同样的问题,你的解释帮了我很多! - Jason Jun Ge
你把这段代码放在哪里? - AlxVallejo

19

我已经用以下代码使其工作:

import axios from 'axios';
import config from '../../configuration.json';
import qs from 'qs';

const baseURL = config['baseUrl_local'];
let authTokenRequest;

/**
  * @description axios instance for ajax requests
*/ 

var client = axios.create({
    baseURL: baseURL,
    headers: {
        appID: 8,
        version: "1.1.0",
        empID: localStorage.getItem('empID'),
        token: localStorage.getItem('accessToken')
    }
});

/**
 * @description this method calls a requestNewToken method to issue a 
 new token to the client
*/ 

 function getAuthToken() {
   if (!authTokenRequest) {
     authTokenRequest = requestNewToken();
     authTokenRequest.then(resetAuthTokenRequest, resetAuthTokenRequest);
   }

   return authTokenRequest;
 }

/**
  * @description this method requests the server to issue a new token, 
  the server response is updated in local storage accessToken
*/ 

function requestNewToken() {
  var newToken = request({
  method: "post",
  url: '/sign-in',
  data:  qs.stringify({
         "userName":localStorage.getItem('userName'),
         "password":localStorage.getItem('password')
         })  
  }).then((res)=>{
  if(res.status == "success"){
    localStorage.setItem('accessToken',res.data.accessToken);
    //if featureArray is present in response object, update the 
    featureArray in local storage
    if(res.data.features){
      localStorage.setItem(
      'featureArray',
     JSON.stringify(res.data.features));
    }
    client = axios.create({
     baseURL: baseURL,
     headers: {
          appID: 8,
          version: "1.1.0",
          empID: localStorage.getItem('empID'),
          token: localStorage.getItem('accessToken')
      }
   });
 } else {
  window.location = "/logout";
 }
});

 return newToken;
}

function resetAuthTokenRequest() {
  authTokenRequest = null;
 }

/**
  * @description if any of the API gets 401 status code, this method 
   calls getAuthToken method to renew accessToken
  * updates the error configuration and retries all failed requests 
  again
*/ 

client.interceptors.response.use(undefined, err => {
  const error = err.response;
  // if error is 401 
  if (error.status===401 && error.config && 
  !error.config.__isRetryRequest) {
  // request for a new token
  return getAuthToken().then(response => {
   // update the error config with new token
   error.config.__isRetryRequest = true;
   error.config.headers.token= localStorage.getItem("accessToken");
   return client(error.config);
  });
 } 
});

/**
 * @description wrapper for making ajax requests
 * @param {object} object with method,url,data etc.
*/ 

const request = function(options) {
  const onSuccess = function(response) {
    return response.data;
  }
 const onError = function(error) {
  //console.error('Request Failed:', error.config);
   if (error.response) {
  //console.error('Status:',  error.response.status);
  //console.error('Data:',    error.response.data);
  //console.error('Headers:', error.response.headers);
  } else {
  console.error('Error Message:', error.message);
  }
 return Promise.reject(error.response || error.message);
 }

return client(options)
        .then(onSuccess)
        .catch(onError);
        options
}

export default request;

【编辑】现在是2019年,这里是另一种对于同样问题的实现。上述解决方案很好,但对于多次失败的请求效果不佳,反而会使用更新后的令牌调用getToken。

 import axios from "axios";

 /* @internal */
 import config from "../config";
 import TokenService from "./token_service"; // Could You please provide code from this file too?

class Request {
    constructor() {
        this.baseURL = config.baseUrl;
        this.isRefreshing = false;
        this.failedRequests = [];
        this.tokenService = new TokenService();
        this.client = axios.create({
            baseURL: config.apiServerBaseUrl,
            headers: {
               clientSecret: this.clientSecret,
            },
        });
        this.beforeRequest = this.beforeRequest.bind(this);
        this.onRequestFailure = this.onRequestFailure.bind(this);
        this.processQueue = this.processQueue.bind(this);
        this.client.interceptors.request.use(this.beforeRequest);
        this.client.interceptors.response.use(this.onRequestSuccess, 
this.onRequestFailure);
}

beforeRequest(request) {
    const token = TokenService.getAccessToken();
    request.headers.Authorization = `Token ${token}`;
    return request;
}

static onRequestSuccess(response) {
    return response.data;
}

async onRequestFailure(err) {
    const { response } = err;
    if (response.status === 401 && err && err.config && !err.config.__isRetryRequest) {
        if (this.isRefreshing) {
            try {
                const token = await new Promise((resolve, reject) => {
                    this.failedRequests.push({ resolve, reject });
                });
                err.config.headers.Authorization = `Bearer ${token}`;
                return this.client(err.config);
            }
            catch (e) {
                return e;
            }
        }
        this.isRefreshing = true;
        err.config.__isRetryRequest = true;
        return new Promise((resolve, reject) => {
            this.tokenService.refreshAccessToken().then((token) => {
                this.tokenService.setAccessToken(token);
                err.config.headers.Authorization = `Bearer ${token}`;
                this.isRefreshing = false;
                this.processQueue(null, token);
                resolve(this.client(err.config));
            }).catch((e) => {
                this.processQueue(e, null);
                reject(err.response);
            });
        });
    }
    throw response;
}

processQueue(error, token = null) {
    this.failedRequests.forEach((prom) => {
        if (error) {
            prom.reject(error);
        } else {
            prom.resolve(token);
        }
       });
        this.failedRequests = [];
    }

}

const request = new Request();

export default request.client;

10

通过这种方法,我们可以捕获axios的401错误。

 axios.post('/add')
 .then(function (response) {...})
 .catch(function (error) {
     console.log(error.response.status) // 401
     console.log(error.response.data.error) //Please Authenticate or whatever returned from server
   if(error.response.status==401){
     //redirect to login
   }
 })

4
当我收到401时,错误中没有任何响应!同时它也没有进入catch块。 - Ali Radmanesh
问题在于我们没有为每个 API 调用添加错误捕获,更好的做法是使用拦截器来操作响应。 - Mark Ezberg

3

我查阅了一些其他问题,这是我的代码:

import axios from 'axios';

const instance = axios.create({
    baseURL: window.location.hostname === 'localhost' ? 'http://localhost:5001/api/v1' : 'https://api.mysite.com/api/v1'
});
instance.defaults.headers.common['Content-Type'] = 'multipart/form-data';

//validate response
instance.interceptors.response.use((response) => {
    return response;
}, (error) => {
        if (error.response.status === 401) {

            return window.location.href = '/login'
        }
    return Promise.reject(error);
});

// Set the AUTH token for any request
instance.interceptors.request.use(
    (config) => {
        const token = localStorage.getItem('token');
        config.headers.Authorization =  token ? `Bearer ${token}` : '';
        return config;
    }
)

export default instance;

只需重定向登录页面,而不是更新令牌并让用户使用服务。 - Mark Ezberg
是的,这是我的业务逻辑!您可以根据您的要求进行定制! - Thanh Nguyen

1

在模块中使用Axios时,我没有找到清晰简洁的答案。您需要将拦截器添加到您正在使用的axios 实例中。

api.js

import axios from 'axios'
import store from '../state'

//Defaults will be combined with the instance
axios.defaults.baseURL = '/some/page.aspx';

//Create Axios Instance
const axiosInstance = axios.create({
    headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json; charset=UTF-8'
    }
});

//Add interceptors to instance
axiosInstance.interceptors.response.use(
    response => response,
    error => {
        if (!error.response) {
            store.commit('setServiceAvailable', false);
        }
        else if (error.response.status === 401) {
            store.commit('setUserAuthorised', false);
        }
        return error;
    });

export default axiosInstance;

然后像平常一样使用

component.js

import api from '../api'
...
async getServersJson() {
    try {
        var response = await api.post('GetReportAsServers', {name: 'get-servers', args: null});
        this.serversJson = this.prettifyJson(response.data.d);
    }
    catch (error) {
        console.error(`Exception getting servers. ${error}`);
    }
},

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