15-新增前端保存token并校验

560 阅读5分钟
回顾

接上篇,上次我们在前端项目中,补全了注册接口和登录接口,但是说实话我们还缺少一些前端记录token的手段。

为什么要记录token?

前端记录令牌(Token)是为了实现身份验证和授权机制,以确保应用程序的安全性和保护用户的数据。以下是前端记录令牌的几个重要原因:

  1. 身份验证:记录令牌是为了验证用户的身份。令牌通常在用户登录后生成,并在每个后续请求中发送到服务器,以证明用户的身份。通过验证令牌,应用程序可以确认用户的身份并为其提供相应的权限和功能。

  2. 授权访问:记录令牌是为了授权用户访问特定资源或执行特定操作。令牌可以包含有关用户的权限和角色信息,通过验证令牌,应用程序可以确定用户是否有权访问特定资源或执行特定操作。

  3. 持久登录:记录令牌可以实现持久登录功能。在某些情况下,用户可能希望在关闭浏览器或重新启动后仍然保持登录状态,而不需要重新输入凭据。通过在前端记录令牌,可以在用户再次访问应用程序时自动恢复其登录状态。

  4. 减少频繁的身份验证请求:记录令牌可以减少对服务器进行频繁身份验证的需要。令牌通常具有较长的有效期,因此在有效期内,可以在前端使用令牌进行身份验证,而无需每次请求都向服务器发送身份验证请求。

  5. 单点登录(SSO):记录令牌可以用于实现单点登录功能。在多个应用程序之间共享令牌,用户只需要进行一次登录,就可以在不同的应用程序中自动登录,而无需重新输入凭据。

通过记录令牌,前端可以维护用户的登录状态、验证用户的身份和权限,并为用户提供定制化的功能和体验。令牌的记录和使用需遵循安全性最佳实践,并根据应用程序的需求进行适当的设置和保护。

技术选型

在前端应用程序中,记录令牌(Token)的常见手段包括:

  1. Local Storage(本地存储):使用 Web 浏览器的本地存储机制(如 Local Storage)将令牌存储在客户端。您可以使用 localStorage.setItem('token', token) 将令牌存储在本地存储中,并使用 localStorage.getItem('token') 从本地存储中检索令牌。

  2. Session Storage(会话存储):使用 Web 浏览器的会话存储机制(如 Session Storage)将令牌存储在客户端。与本地存储类似,您可以使用 sessionStorage.setItem('token', token) 将令牌存储在会话存储中,并使用 sessionStorage.getItem('token') 从会话存储中检索令牌。会话存储的区别在于,会话结束后会自动清除存储的数据。

  3. Cookie(HTTP Cookie):使用 HTTP Cookie 将令牌存储在客户端。您可以使用 document.cookie = 'token=' + token 将令牌设置为 Cookie,并通过 document.cookie 获取 Cookie 的值。请注意,使用 Cookie 存储令牌时需要注意安全性和防止跨站点脚本攻击(XSS)。

  4. 内存变量:在前端应用程序的内存中保持令牌的变量。这种方式仅在应用程序运行期间有效,刷新页面或关闭浏览器后令牌将丢失。您可以在适当的位置定义一个变量,并将令牌赋值给它。

  5. Vuex(Vue.js 应用程序)或 Redux(React 应用程序):对于使用 Vuex 或 Redux 等状态管理库的应用程序,可以将令牌存储在状态管理库的状态中。通过在应用程序的状态中保持令牌,可以在整个应用程序中共享和访问令牌。

无论您选择哪种方式,都应该根据安全性和隐私性需求来选择合适的存储方式,并确保适当地处理令牌的验证和过期问题。另外,根据所处的环境和应用程序的特性,选择最适合您应用程序的令牌存储方式。在此,我选择的是本地存储方案,自己用,安全级别要求没那么高。

编码

新增前端配置常量内容

const CONFIG = {
    SERVER_URL: "http://127.0.0.1:9923",
    JWT_KEY:  'AbandonToken',
    LOGIN_PATH:  'login'
}

新增请求token接口

新增abandon-web/src/services/auth.ts

import {request} from "@umijs/max";
import CONFIG from "../../config/server_config";

export interface PityResponse {
  code: number;
  data?: any;
  msg?: string;
}

export interface LoginResponse {
  token: string;
  expire: number;
  user: LoginUser;
}

export interface LoginUser {
  id: number;
  username: string;
  name: string;
  email: string;

  avatar: string;
  created_at: string;
  deleted_at: number;

  is_valid: boolean;
  last_login_at: string;

  role: number;
  update_user: number | null;
  updated_at: string;
}

export async function currentUser(params: Record<string, string>) {
  return request<{
    data: LoginUser;
    msg?: string;
    code: number;
  }>(`${CONFIG.SERVER_URL}/auth/query`, {
    method: 'GET',
    params,
  });
}

更改abandon-web/src/pages/Login/index.ts文件,新增保存token到浏览器本地逻辑:

image.png

新增abandon-web/src/app.tsx,删除原本的abandon-web/src/app.ts文件,编辑以下内容:

import {PageLoading, Settings as LayoutSettings} from '@ant-design/pro-components';
import {history} from '@umijs/max';
import defaultSettings from '../config/defaultSettings';
import {errorConfig} from './requestErrorConfig';
import React from 'react';
import {currentUser as queryCurrentUser, LoginUser} from './services/auth';
import {message} from "antd";
import CONFIG from "../config/server_config";

/**
 * @see  https://umijs.org/zh-CN/plugins/plugin-initial-state
 * */
export async function getInitialState(): Promise<{
    settings?: Partial<LayoutSettings>;
    currentUser?: LoginUser;
    loading?: boolean;
    fetchUserInfo?: () => Promise<LoginUser | undefined>;
}> {
    const fetchUserInfo = async () => {
        // 定义一个异步函数 fetchUserInfo,用于获取用户信息。
        try {
            const token = localStorage.getItem(CONFIG.JWT_KEY);
            // 从本地存储(localStorage)中获取名为 "AbandonToken" 的令牌。
            if (!token) {
                // 检查令牌是否存在。如果令牌不存在,使用 history.push(loginPath) 将页面重定向到登录页。
                history.push(CONFIG.LOGIN_PATH);
                return;
            }
            const msg = await queryCurrentUser({token});
            //使用获取到的令牌调用 queryCurrentUser 函数来获取用户信息。
            if (msg.code !== 200) {
                //检查返回的用户信息中的状态码是否为 200。如果不是 200,显示信息提示,并抛出错误。
                message.info(msg.msg);
                throw msg.msg;
            }
            return msg.data;
        } catch (error) {
            //捕获可能出现的错误。将页面重定向到登录页。
            history.push(CONFIG.LOGIN_PATH);
        }
        return undefined;
    };
    // 如果不是登录页面,执行
    const {location} = history;
    if (location.pathname !== CONFIG.LOGIN_PATH) {
        const currentUser = await fetchUserInfo();
        return {
            fetchUserInfo,
            currentUser,
            settings: defaultSettings,
        };
    }

    return {
        fetchUserInfo,
        settings: defaultSettings,
    };
}

/**
 * @name request 配置,可以配置错误处理
 * 它基于 axios 和 ahooks 的 useRequest 提供了一套统一的网络请求和错误处理方案。
 * @doc https://umijs.org/docs/max/request#配置
 */
export const request = {
    ...errorConfig,
};
验证

可以看到我们的本地存储空间中,是存在我们新增的token的 image.png

随着在home页面每次刷新,都会发送一个query请求

image.png

如果token超出我们设置的时间,则会弹出我们后端query接口描述的内容

image.png