【问题标题】:Can't access the JWT Web Token from response fetch无法从响应获取中访问 JWT Web 令牌
【发布时间】:2021-08-20 22:19:27
【问题描述】:

我尝试从 API 获取响应中提取 JSON Web 令牌并将其存储在本地存储中以供以后使用,但我无法完全弄清楚。我尝试使用

response.data.token

,但是当我登录时获取错误。我在这里做错了什么?

获取代码:

    async executeLogin (payload) {
      try {
        const response = await fetch('http://localhost:3030/authentication', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json'
          },
          body: JSON.stringify(payload) // body data type must match "Content-Type" header
        })
        if (response.ok) {
          console.log(response.data.token)
          alert('You logged in')
          this.$store.commit('setAuthentication', true)
          this.$router.replace({ name: 'Main' })
        }
        this.$refs.form.reset()
      } catch (err) {
        alert('Something went wrong...')
      }

【问题讨论】:

  • 您在 headers发送 JWT 令牌。如果您收到一个,那么该响应是什么样的?这没有硬性标准
  • 您需要像这样将响应序列化为 json - response.json()
  • 通过使用 response.json() 我收到了一个 Promise {},其中有 accessToken。但是我现在如何从那里提取它呢?
  • 您必须等待承诺,然后您才能访问响应正文。使用async/await语法

标签: javascript authentication jwt


【解决方案1】:

除了评论: 在来自服务器的响应中,我可以接收 不同 格式的数据,不仅仅是 json,因此我在标头中发送刷新令牌并相应地接收。另外,在你的令牌过期的那一刻,你只能发送一个请求,直到你收到一个刷新令牌,所以我实现了等待其他请求。这是 fetch 函数的包装器

const generalHeaders = {};

const b64DecodeUnicode = (str) => decodeURIComponent(atob(str).split('').map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)).join(''));

let initiationWasStart = false;
let initiated = false;
let userSignedIn = false;
let tokenExp = 0;
let refreshTokenRequestWasStart = false;

const getToken = () => /* TODO get current token from store here */;

export const userWasSignedIn = (newToken) => {
    updateGeneralHeaders({token: newToken});
    userSignedIn = true;
};
export const userWasLogout = () => {
    updateGeneralHeaders({token: null});
    userSignedIn = false;
};

export const requestAllowed = () => {
    initClient();
    if (!initiated) return false;
    if (!userSignedIn) {
        refreshTokenRequestWasStart = false;
        return true;
    }
    if (tokenIsValid()) {
        refreshTokenRequestWasStart = false;
        return true;
    }
    if (lastToken !== getToken()) {
        updateGeneralHeaders({token: getToken()});
        return false;
    }
    if (refreshTokenRequestWasStart) return false;
    refreshTokenRequestWasStart = true;
    return true;
};

export const initClient = () => {
    if (initiationWasStart || initiated) return;
    initiationWasStart = true;
    updateGeneralHeaders({token: getToken() || null});
    initiated = true;
};

export const tokenIsValid = () => Date.now() < tokenExp;

let lastToken;

export const updateGeneralHeaders = (newHeaders = {}) => {
    const {token: newToken} = newHeaders;
    if (typeof newToken === 'string') {
        try {
            const {exp} = JSON.parse(b64DecodeUnicode(newToken.split('.')[1]));
            const newTokenExp = exp * 1000;
            if (Date.now() < newTokenExp || newTokenExp > tokenExp) {
                tokenExp = newTokenExp;
                lastToken = newToken;
                /*
                TODO save token to store here
                */
                refreshTokenRequestWasStart = false;
            }
        } catch (e) {
            console.error(e);
        }
    }
    Object.assign(generalHeaders, newHeaders);
};

export const getRefreshTokenFromResponse = (response) => {
    let refreshToken = null;
    response.headers.forEach((value, header) => {
        if (header === 'refresh_token') {
            userWasSignedIn(value);
            refreshToken = value;
        }
    });
    return refreshToken;
};

export const contentTypes = {
    JSON: 'application/json',
    BLOB: 'multipart/form-data'
};

export const fetchMethods = {
    DELETE: 'DELETE',
    POST: 'POST',
    PUT: 'PUT',
    GET: 'GET'
};

export const fetcher = (endpoint, {method = fetchMethods.GET, data = {}} = {}) => {
    const [url, body, headers] = (() => {
        switch (method) {
            case fetchMethods.DELETE:
            case fetchMethods.GET: {
                const parameters = Object.keys(data).filter(p => {
                    if (Array.isArray(data[p])) {
                        return data[p].length > 0 && data[p][0] !== '';
                    }
                    return data[p] === 0 || !!data[p];
                }).map(k => `${encodeURIComponent(k)}=${encodeURIComponent(data[k])}`).join('&');
                return [parameters.length > 0 ? `${endpoint}${endpoint.indexOf('?') > -1 ? '&' : '?'}${parameters}` : endpoint, undefined, {}];
            }
            case fetchMethods.PUT:
            case fetchMethods.POST:
            default: {
                let body, contentType;
                if (data instanceof FormData) {
                    body = data;
                    contentType = null;
                } else {
                    body = JSON.stringify(data);
                    contentType = contentTypes.JSON;
                }
                return [endpoint, body, {
                    ...contentType && ({'Content-Type': contentType})
                }];
            }
        }
    })();

    const options = {
        method,
        headers,
        body
    };

    return new Promise((resolve, reject) => {
        const doFetch = () => {
            (async () => {
                options.headers.token = getToken();
                const response = await fetch(url, options);
                const refreshToken = getRefreshTokenFromResponse(response);
                const contentType = response.headers.get('content-type');
                const contentTypeResult = typeof contentType === 'string' ? contentType.toLowerCase() : null;
                switch (contentTypeResult) {
                    case 'application/json': {
                        const result = await response.json();
                        if (result.error) {
                            switch (result.error.message.toLowerCase().split(':')[0]) {
                                case 'invalid token': {
                                    userWasLogout();
                                    /* TODO logout here */
                                    reject(result);
                                    break;
                                }
                                default: reject(result);
                            }
                        } else if (result.data !== 0 && result.data !== false && !result.data) {
                            reject({error: {message: 'error'}, url, options});
                        } else {
                            resolve(result.data, refreshToken);
                        }
                        break;
                    }
                    case 'image/gif':
                    case 'image/png':
                    case 'image/jpg':
                    case 'image/jpeg':
                    case 'image/webp': {
                        resolve(await response.blob(), refreshToken);
                        break;
                    }
                    case null:
                    default: {
                        reject({error: {message: 'error'}, url, options});
                        break;
                    }
                }
            })();
        };

        if (requestAllowed()) {
            doFetch();
            return;
        }

        const checker = setInterval(async () => {
            if (requestAllowed()) {
                clearInterval(checker);
                doFetch();
            }
        }, 1000);
    });
};

另外,如果您在标头中发送刷新令牌,则需要在此之前在您的服务器上设置 Access-Control-Expose-Headers 标头

【讨论】:

    猜你喜欢
    • 2020-06-12
    • 1970-01-01
    • 2020-10-09
    • 2021-04-20
    • 2023-02-14
    • 2018-01-13
    • 2021-02-13
    • 1970-01-01
    • 2018-10-10
    相关资源
    最近更新 更多