上一篇:React 网络请求 对接登录 API

前端存储

localstorage

localStorage 方法存储的数据没有时间限制

存储方式:

以键值对(Key-Value)方式存储,永久存储,永不失效,除非手动删除

常用 API:

getItem // 获取记录

setIten// 保存记录

removeItem //移除记录

clear // 清除记录

sessionstorage

HTML5 本地存储 API 中 localStorage 与 sessionStorage 在使用方法上是相同的

区别在于 sessionStorage 在关闭页面后即被清空,而 localStorage 则会一直保存

项目中使用

存储令牌

  • 封装 localStorage API,暴露提供操作方法

utils.js添加几个操作方法

// 保存
export const saveAccessToken = (token) => {
  localStorage.setItem(MOOSE_REACT_LEARN_ACCESS_TOKEN, token);
};

// 获取
export const getAccessToken = () => {
  return localStorage.getItem(MOOSE_REACT_LEARN_ACCESS_TOKEN) || 'MOOSE_REACT_LEARN';
};

// 移除
export const removeAccessToken = () => {
  localStorage.removeItem(MOOSE_REACT_LEARN_ACCESS_TOKEN);
};

// 封装参数放到请求头中
export const getAuthorization = () => {
  let accessToken = getAccessToken();
  return isEmpty(accessToken)
    ? {}
    : {
        Authorization: `Bearer ${getAccessToken()}`,
      };
};

// 为登录时,重定向到 登录界面
export const redirectLogin = () => {
  const { redirect } = getPageQuery(); // Note: There may be security issues, please note
  if (window.location.pathname !== '/login' && !redirect) {
    removeAccessToken();
    history.replace({
      pathname: '/login',
      search: stringify({
        redirect: window.location.href,
      }),
    });
  }
};
  • 在登录完成之后,使用 localStorage 保存接口返回的 AccessToken

修改登录接口逻辑


import { saveAccessToken } from '@/utils/utils';

......
*login({ payload }, { call, put }) {
// 提交处理后的参数
      const response = yield call(postAccountLogin, params);
      yield put({
        type: 'changeLoginStatus',
        payload: response,
      }); // Login successfully
      if (response.code === 200) {

        // 添加这一行
        saveAccessToken(response.data);

        const urlParams = new URL(window.location.href);
......
  • 每次请求的时候把 accessToken 放到请求头中一起发送

修改 request.js 封装,添加 umi-request请求拦截器

request.interceptors.request.use(
  (url, options) => {
    // console.log('request.interceptors.request', options);
    // 可以设置一些其他的属性
    return {
      url,
      options: { ...options, headers: { ...options.headers, ...getAuthorization() } },
    };
  },
  { global: true },
);

修改 SecurityLayout, userId 和返回到字段一致

修改 AvatarDropdown,userName和返回到字段一致

刷新令牌

OAuth2.0 密码模式授权,会返回 access_token 和 refresh_token

服务端接口在登录完成后只返回 access_token, refresh_token 保存到服务端(redis)

refresh_token 失效时间设置要比 access_token 长一点,确保使用 refresh_token 能够请求到 access_token

当 access_token 失效,前端使用上一次 access_token 请求 refresh_token,返回 access_token,继续再次请求

参考

  • Spring Boot 2.x 快速入门
  • SpringBoot OAuth2.0 认证授权(密码模式)
  • SpringBoot OAuth2.0 封装登录、刷新令牌接口

API 接口返回 code 需要和前端约定好

  • Java 服务接口定义 code
  /**
   * token
   */
  TOKEN_IS_EMPTY(-10101, "token must not be null"),
  TOKEN_VALIDATE_FAIL(-10102, "token check fail"),
  TOKEN_INVALID(-10103, "invalid access token"),
  REFRESH_TOKEN_NOT_EXIST(-10104, "refresh token not exist"),
  ACCESS_TOKEN_IS_EMPTY(-10105, "access token is empty"),
  • 调用刷新令牌接口 services/token.js
import { getAccessToken } from '@/utils/utils';
import request from '@/utils/request';

export async function postRefreshToken() {
  return request('/api/v1/token/refresh', {
    method: 'POST',
    requestType: 'form',
    data: {
      accessToken: getAccessToken(),
    },
  });
}
  • 修改 request.js
// 重试函数
const retry = (response, options) => {
  return request(response.url, options);
};

request.interceptors.response.use(async (response, options) => {
  // 复制上一次请求
  const { status } = await response.clone();
  if (status === 401) {
    const { code: normalCode } = await response.clone().json();
    if (normalCode === 401) {
      redirectLogin();
      return;
    }

    // -10103 代表当次请求 token 无效
    if (normalCode === -10103) {
      const { data, code: refreshCode } = await postRefreshToken();

      // 当获取刷新令牌返回 code
      if (refreshCode === -10101 || refreshCode === -10102 || refreshCode === -10104) {
        redirectLogin();
        return;
      }
      saveAccessToken(data);
      return retry(response, options);
    }
  }
  return response;
});

这个时候可以在accessToken 时间过期之后,使用 refreshToken 再次获取 accessToken 继续请求

关注公众号 「全栈技术部」,不断学习更多有趣的技术知识。

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐