回顾
接上篇,上次我们在前端项目中,补全了注册接口和登录接口,但是说实话我们还缺少一些前端记录token的手段。
为什么要记录token?
前端记录令牌(Token)是为了实现身份验证和授权机制,以确保应用程序的安全性和保护用户的数据。以下是前端记录令牌的几个重要原因:
-
身份验证:记录令牌是为了验证用户的身份。令牌通常在用户登录后生成,并在每个后续请求中发送到服务器,以证明用户的身份。通过验证令牌,应用程序可以确认用户的身份并为其提供相应的权限和功能。
-
授权访问:记录令牌是为了授权用户访问特定资源或执行特定操作。令牌可以包含有关用户的权限和角色信息,通过验证令牌,应用程序可以确定用户是否有权访问特定资源或执行特定操作。
-
持久登录:记录令牌可以实现持久登录功能。在某些情况下,用户可能希望在关闭浏览器或重新启动后仍然保持登录状态,而不需要重新输入凭据。通过在前端记录令牌,可以在用户再次访问应用程序时自动恢复其登录状态。
-
减少频繁的身份验证请求:记录令牌可以减少对服务器进行频繁身份验证的需要。令牌通常具有较长的有效期,因此在有效期内,可以在前端使用令牌进行身份验证,而无需每次请求都向服务器发送身份验证请求。
-
单点登录(SSO):记录令牌可以用于实现单点登录功能。在多个应用程序之间共享令牌,用户只需要进行一次登录,就可以在不同的应用程序中自动登录,而无需重新输入凭据。
通过记录令牌,前端可以维护用户的登录状态、验证用户的身份和权限,并为用户提供定制化的功能和体验。令牌的记录和使用需遵循安全性最佳实践,并根据应用程序的需求进行适当的设置和保护。
技术选型
在前端应用程序中,记录令牌(Token)的常见手段包括:
-
Local Storage(本地存储):使用 Web 浏览器的本地存储机制(如 Local Storage)将令牌存储在客户端。您可以使用
localStorage.setItem('token', token)将令牌存储在本地存储中,并使用localStorage.getItem('token')从本地存储中检索令牌。 -
Session Storage(会话存储):使用 Web 浏览器的会话存储机制(如 Session Storage)将令牌存储在客户端。与本地存储类似,您可以使用
sessionStorage.setItem('token', token)将令牌存储在会话存储中,并使用sessionStorage.getItem('token')从会话存储中检索令牌。会话存储的区别在于,会话结束后会自动清除存储的数据。 -
Cookie(HTTP Cookie):使用 HTTP Cookie 将令牌存储在客户端。您可以使用
document.cookie = 'token=' + token将令牌设置为 Cookie,并通过document.cookie获取 Cookie 的值。请注意,使用 Cookie 存储令牌时需要注意安全性和防止跨站点脚本攻击(XSS)。 -
内存变量:在前端应用程序的内存中保持令牌的变量。这种方式仅在应用程序运行期间有效,刷新页面或关闭浏览器后令牌将丢失。您可以在适当的位置定义一个变量,并将令牌赋值给它。
-
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到浏览器本地逻辑:
新增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的
随着在home页面每次刷新,都会发送一个query请求
如果token超出我们设置的时间,则会弹出我们后端query接口描述的内容