当前位置: 首页 > news >正文

0805登录_注册_token_用户信息_退出-网络ajax请求2-react-仿低代码平台项目

文章目录

    • 1 JWT
      • 1.1 JWT结构
      • 1.2 工作流程
      • 1.3 优点
      • 1.4 缺点
      • 1.5 安全实践
      • 1.6. 适用场景
      • 1.7 JWT与OAuth2
      • **8. 示例代码(Node.js)**
    • 2 用户mock和api
    • 3 注册
    • 4 登录
    • 5 token存储
    • 6 请求拦截器设置token
    • 6 获取用户信息
    • 7 退出登录
    • 结语

1 JWT

JSON Web Token(JWT)是一种开放标准(RFC 7519),用于在各方之间安全传输信息。它通过数字签名确保数据的完整性和可信性,常用于身份验证和授权。以下是JWT的详细介绍:


1.1 JWT结构

JWT由三部分组成,用点(.)分隔:

  • Header(头部)
    包含令牌类型(typ: "JWT")和签名算法(如alg: HS256)。
    示例:{"alg": "HS256", "typ": "JWT"} → Base64Url编码后形成第一部分。
  • Payload(载荷)
    存放声明(claims),包括预定义声明(如用户ID、过期时间)和自定义数据。
    常见预定义声明:
    • iss(签发者)、exp(过期时间)、sub(主题)、aud(受众)等。
      示例:{"sub": "123", "name": "Alice", "exp": 1516239022} → Base64Url编码后形成第二部分。
  • Signature(签名)
    对前两部分的签名,防止数据篡改。算法由Header指定(如HMAC SHA256)。
    生成方式:HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

最终JWT形式:xxxxx.yyyyy.zzzzz


1.2 工作流程

  1. 用户登录:客户端发送凭证(如用户名/密码)到服务器。
  2. 生成JWT:服务器验证凭证,生成并返回JWT。
  3. 客户端存储:客户端保存JWT(通常存在localStorage或Cookie中)。
  4. 携带令牌请求:客户端在请求头中添加Authorization: Bearer <JWT>
  5. 服务器验证:服务器检查签名有效性、过期时间等,验证通过后处理请求。

1.3 优点

  • 无状态:无需服务器存储会话信息,适合分布式系统。
  • 跨域支持:适用于API网关、单页应用(SPA)等场景。
  • 灵活性:载荷可自定义扩展,传递非敏感用户信息。

1.4 缺点

  • 不可废止:令牌到期前无法强制失效,需借助黑名单或短过期时间。
  • 存储风险:客户端存储不当可能导致XSS攻击窃取令牌。
  • 信息暴露:载荷仅Base64编码,需避免存放敏感数据。

1.5 安全实践

  • 使用HTTPS:防止令牌在传输中被截获。
  • 强签名算法:如HMAC SHA256或RSA,避免弱算法(如HS256密钥过短)。
  • 合理设置过期时间:缩短令牌有效期,减少泄露风险。
  • 敏感数据加密:必要时使用JWE(JSON Web Encryption)加密载荷。

1.6. 适用场景

  • API认证:RESTful API的无状态身份验证。
  • 单点登录(SSO):跨多个系统的用户身份共享。
  • 移动端应用:减少频繁查询数据库的开销。

1.7 JWT与OAuth2

  • JWT常用作OAuth2的Bearer Token,传递用户身份和权限。
  • OAuth2定义授权流程,JWT是实现令牌的一种方式。

8. 示例代码(Node.js)

const jwt = require('jsonwebtoken');// 生成JWT
const token = jwt.sign({ userId: 123, role: 'admin' },'your-secret-key',{ expiresIn: '1h' }
);// 验证JWT
jwt.verify(token, 'your-secret-key', (err, decoded) => {if (err) throw err;console.log(decoded); // { userId: 123, role: 'admin', iat: ..., exp: ... }
});

通过理解JWT的结构、流程及安全实践,开发者可以有效利用其在现代Web应用中实现安全、高效的身份验证。

2 用户mock和api

用户mock,user.js代码如下所示:

const Mock = require('mockjs')
const Random = Mock.Randommodule.exports = [{// 获取用户url: '/api/user/info',method: 'get',response() {return {errno: 0,data: {username: Random.title(),nickname: Random.cname(),},}}},{// 注册新用户url: '/api/user/register',method: 'post',response() {return {errno: 0}}},{// 用户登录url: '/api/user/login',method: 'post',response() {return {errno: 0,data: {token: Random.word(20)},}}},
]

前端user.ts 用户api接口代码如下所示:

 import request, { ResDataType } from "../services/request";/*** 获取用户信息* @returns  用户信息*/
export async function getUserInfoApi(): Promise<ResDataType> {const url = "/api/user/info";const data = (await request.get(url)) as ResDataType;return data;
}/*** 注册新用户* @returns  注册是否成功*/
export async function registerApi(username: string,password: string,nickname?: string
): Promise<ResDataType> {const url = "/api/user/register";const body = { username, password, nickname: nickname || username };const data = (await request.post(url, body)) as ResDataType;return data;
}/*** 用户登录* @returns  token*/
export async function loginApi(username: string,password: string
): Promise<ResDataType> {const url = "/api/user/login";const data = (await request.post(url, { username, password })) as ResDataType;return data;
}

3 注册

Register.tsx代码如下所示:

import { FC } from "react";
import { Link, useNavigate } from "react-router-dom";
import { Typography, Space, Form, Input, Button, message } from "antd";
import { UserAddOutlined } from "@ant-design/icons";
import { useRequest } from "ahooks";import { LOGIN_PATHNAME } from "../router";
import { registerApi } from "@/api/user";import styles from "./Register.module.scss";const { Title } = Typography;const Register: FC = () => {const nav = useNavigate();const { run: handleRegister } = useRequest(async (values) => {const { username, password, nickname } = values;return await registerApi(username, password, nickname);},{manual: true,onSuccess() {message.success("注册成功");// 跳转登录页nav(LOGIN_PATHNAME);},});function onFinish(values: any) {handleRegister(values);}return (<div className={styles.container}><div><Space><Title level={2}><UserAddOutlined /></Title><Title level={2}>注册新用户</Title></Space></div><div><FormlabelCol={{ span: 6 }}wrapperCol={{ span: 16 }}onFinish={onFinish}><Form.Itemlabel="用户名"name="username"rules={[{ required: true, message: "请输入用户名" },{type: "string",min: 5,max: 20,message: "字符长度再5-20之间",},{pattern: /^\w+$/,message: "只能是字母数字下划线",},]}><Input /></Form.Item><Form.Itemlabel="密码"name="password"rules={[{ required: true, message: "请输入用户名" },{min: 8,message: "密码长度最少8位",},]}><Input.Password /></Form.Item><Form.Itemlabel="确认密码"name="confirm"dependencies={["password"]}rules={[{required: true,message: "请输入确认密码",},({ getFieldValue }) => ({validator(_, value) {if (!value || getFieldValue("password") === value) {return Promise.resolve();} else {return Promise.reject(new Error("两次密码不一致"));}},}),]}><Input.Password /></Form.Item><Form.Item label="昵称" name="nickname"><Input /></Form.Item><Form.Item wrapperCol={{ offset: 6, span: 16 }}><Space><Button type="primary" htmlType="submit">注册</Button><Link to={LOGIN_PATHNAME}>已有账户,登录</Link></Space></Form.Item></Form></div></div>);
};
export default Register;

执行注册,成功挑战登录页,如下图所示:在这里插入图片描述

4 登录

登录页Login.tsx代码如下所示:

import { FC, useEffect } from "react";
import { Link, useNavigate } from "react-router-dom";
import { Typography, Space, Form, Input, Button, Checkbox, message } from "antd";
import { UserAddOutlined } from "@ant-design/icons";
import { useRequest } from "ahooks";import { MANAGE_INDEX_PATHNAME, REGISTER_PATHNAME } from "../router";
import { loginApi } from "@/api/user";import styles from "./Register.module.scss";const { Title } = Typography;const USERNAME_KEY = "username";
const PASSWORD_KEY = "password";/*** 浏览器本地存储用户信息* @param username 用户名* @param password 密码*/
function rememberUser(username: string, password: string) {localStorage.setItem(USERNAME_KEY, username);localStorage.setItem(PASSWORD_KEY, password);
}/*** 浏览器本地删除用户信息* @param username 用户名* @param password 密码*/
function deleteUserFromStorage(username: string, password: string) {localStorage.removeItem(USERNAME_KEY);localStorage.removeItem(PASSWORD_KEY);
}/*** 浏览器本地获取用户信息*/
function getUserInfoFromStorage() {return {username: localStorage.getItem(USERNAME_KEY),password: localStorage.getItem(PASSWORD_KEY),};
}const Login: FC = () => {const nav = useNavigate()// 表单组件初始化const [form] = Form.useForm();useEffect(() => {const { username, password } = getUserInfoFromStorage();form.setFieldsValue({ username, password });// eslint-disable-next-line react-hooks/exhaustive-deps}, []);const { run: handleLogin } = useRequest(async (values) => {const { username, password } = values;return await loginApi(username, password);},{manual: true,onSuccess(res) {message.success("登录成功")// todo 存储token// 跳转我的问卷nav(MANAGE_INDEX_PATHNAME)},});function onFinish(values: any) {const { username, password, remember } = values || {};if (remember) {rememberUser(username, password);} else {deleteUserFromStorage(username, password);}handleLogin({ username, password });}return (<div className={styles.container}><div><Space><Title level={2}><UserAddOutlined /></Title><Title level={2}>用户登录</Title></Space></div><div><FormlabelCol={{ span: 6 }}wrapperCol={{ span: 16 }}onFinish={onFinish}initialValues={{ remember: true }}form={form}><Form.Itemlabel="用户名"name="username"rules={[{ required: true, message: "请输入用户名" },{type: "string",min: 5,max: 20,message: "字符长度再5-20之间",},{pattern: /^\w+$/,message: "只能是字母数字下划线",},]}><Input /></Form.Item><Form.Itemlabel="密码"name="password"rules={[{ required: true, message: "请输入用户名" },{min: 8,message: "密码长度最少8位",},]}><Input.Password /></Form.Item><Form.ItemwrapperCol={{ offset: 6, span: 16 }}name="remember"valuePropName="checked"><Checkbox>记住我</Checkbox></Form.Item><Form.Item wrapperCol={{ offset: 6, span: 16 }}><Space><Button type="primary" htmlType="submit">登录</Button><Link to={REGISTER_PATHNAME}>注册新用户</Link></Space></Form.Item></Form></div></div>);
};
export default Login;

登录成功后跳转我的问卷也,如下图所示:在这里插入图片描述

5 token存储

用户登录成功后,需要存储token,userToken.ts代码如下所示

/*** @description localStorage管理用户token* @author gaogzhen*/const KEY = "USER-TOKEN"/*** 设置token* @param token */
export function setToken(token:string) {localStorage.setItem(KEY, token)  
}/*** 获取token* @returns token*/
export function getToken() {return localStorage.getItem(KEY) || ''
}/*** 删除token*/
export function removeToken() {localStorage.removeItem(KEY)
}

登录页登录成功后,执行存储token,Login.tsx代码如下:

  const { run: handleLogin } = useRequest(async (values) => {const { username, password } = values;return await loginApi(username, password);},{manual: true,onSuccess(res) {message.success("登录成功");// 存储tokenconst { token = "" } = res;setToken(token);// 跳转我的问卷nav(MANAGE_INDEX_PATHNAME);},});

localStorage存储如下图哦所示:在这里插入图片描述

6 请求拦截器设置token

登录成功后,用户每次请求需要携带token,用户身份验证、权限验证等。这里通过请求拦截器实现,request.ts代码如下所示:

import axios from "axios";
import { message } from "antd";
import { AUTHORIZATION } from "@/constant";
import { getToken } from "@/utils/userToken";const request = axios.create({timeout: 5000,
});// request拦截:每次请求携带token
request.interceptors.request.use((config) => {// todo token 校验config.headers[AUTHORIZATION] = `Bearer ${getToken()}`;return config;
});// response 拦截:统一处理errno和msg
request.interceptors.response.use((res) => {const resData = (res.data || {}) as ResType;const { errno, data, msg } = resData;if (errno !== 0) {// 错误提示if (msg) {message.error(msg);}throw new Error(msg);}return data as any;
});
export default request;export type ResDataType = {[key: string]: any;
};export type ResType = {errno: number;data?: ResDataType;msg?: string;
};

效果如下图所示:

在这里插入图片描述

6 获取用户信息

用户登录之后,用户信息很多地方需要使用,在学习状态管理之后再处理,这里我们暂时在用户信息组件处理。

用户信息UserInfo.tsx代码如下所示:

import { FC } from "react";
import { Link } from "react-router-dom";
import { LOGIN_PATHNAME } from "../router/index";
import { useRequest } from "ahooks";
import { getUserInfoApi } from "@/api/user";
import { UserOutlined } from "@ant-design/icons";
import { Button } from "antd";const UserInfo: FC = () => {const { data } = useRequest(getUserInfoApi);const { username, nickname } = data || {};const User = (<><span style={{ color: "#e8e8e8" }}><UserOutlined />{nickname}</span><Button type="link">退出</Button></>);const Login = <Link to={LOGIN_PATHNAME}>登录</Link>;return <>{username ? User : Login}</>;
};
export default UserInfo;

效果如下图所示:

在这里插入图片描述

7 退出登录

UserInfo.tsx退出功能代码如下所示:

import { FC } from "react";
import { Link, useNavigate } from "react-router-dom";
import { useRequest } from "ahooks";
import { Button } from "antd";
import { UserOutlined } from "@ant-design/icons";import { LOGIN_PATHNAME } from "../router/index";
import { getUserInfoApi } from "@/api/user";
import { removeToken } from "@/utils/userToken";const UserInfo: FC = () => {const nav = useNavigate()const { data } = useRequest(getUserInfoApi);const { username, nickname } = data || {};function logout() {removeToken()// 跳转登录页nav(LOGIN_PATHNAME)}const User = (<><span style={{ color: "#e8e8e8" }}><UserOutlined />{nickname}</span><Button type="link" onClick={logout}>退出</Button></>);const Login = <Link to={LOGIN_PATHNAME}>登录</Link>;return <>{username ? User : Login}</>;
};
export default UserInfo;

  • 执行退出,但是右上角还是显示登录状态,后面处理

结语

❓QQ:806797785

⭐️仓库地址:https://gitee.com/gaogzhen

⭐️仓库地址:https://github.com/gaogzhen

[1]ahook官网[CP/OL].

[2]mock文档[CP/OL].

[3]Ant Design官网[CP/OL].

http://www.xdnf.cn/news/191899.html

相关文章:

  • DeepSeek+Dify之三工作流引用知识库案例
  • 第十四章-PHP与HTTP协议
  • Async/Await 必须使用 try/catch 吗?
  • 大模型如何应对内容安全:原理、挑战与技术路径探讨
  • Webug4.0通关笔记02- 第2关布尔注入与第3关延时注入
  • ubantu18.04(Hadoop3.1.3)之Flink安装与编程实践(Flink1.9.1)
  • PostgreSQL与MySQL哪个适合做时空数据分析?
  • 安达发|高效智能塑料切割数控系统 - 全自动化软件解决方案
  • 信创时代技术栈选择与前景分析:国产替代背景下的战略路径与实践指南
  • 穷鬼计划:react+tailwindcss+vercel
  • Git-基本操作
  • 【MCP Node.js SDK 全栈进阶指南】高级篇(1):MCP多服务器协作架构
  • 15、项目搭建:绘制城堡蓝图——React 19 工程配置
  • Linux网络编程:TCP多进程/多线程并发服务器详解
  • OceanBase数据库-学习笔记4-租户
  • 100%提升信号完整性:阻抗匹配在高速SerDes中的实践与影响
  • 7、langChain和RAG实战:基于LangChain和RAG的常用案例实战
  • 已有 npm 项目,如何下载依赖、编译并运行项目
  • 【Kubernetes】部署 Kubernetes 仪表板(Dashboard)
  • C++ STL编程 vector的插入、删除、扩容机制、随机访问和内存交换
  • 安卓基础(HashMap和ArrayList)
  • 测试—概念篇
  • 回归问题常用模型以及优缺点和使用场景
  • Uniapp:vite.config.js全局配置
  • V Rising 夜族崛起 [DLC 解锁] [Steam] [Windows SteamOS]
  • DBeaver CE 24.1.3 (Windows 64位) 详细安装教程
  • 基于SpringBoot的食物营养分析与推荐网站系统
  • 如何在idea中写spark程序。
  • leetcode11-盛水最多的容器
  • AG32VF407VG的VREFP是否可以输入2.5V的参考电压