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

[复制链接]
发表于 2025-9-22 05:29:39 | 显示全部楼层 |阅读模式
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 工作流程


  • 用户登录:客户端发送凭证(如用户名/暗码)到服务器
  • 天生JWT服务器验证凭证,天生并返回JWT。
  • 客户端存储:客户端生存JWT(通常存在localStorage或Cookie中)。
  • 携带令牌哀求:客户端在哀求头中添加Authorization: Bearer <JWT>。
  • 服务器验证:服务器查抄签名有效性、过期时间等,验证通过后处置惩罚哀求。

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)

  1. const jwt = require('jsonwebtoken');
  2. // 生成JWT
  3. const token = jwt.sign(
  4.   { userId: 123, role: 'admin' },
  5.   'your-secret-key',
  6.   { expiresIn: '1h' }
  7. );
  8. // 验证JWT
  9. jwt.verify(token, 'your-secret-key', (err, decoded) => {
  10.   if (err) throw err;
  11.   console.log(decoded); // { userId: 123, role: 'admin', iat: ..., exp: ... }
  12. });
复制代码

通过明白JWT的结构、流程及安全实践,开发者可以有效利用其在今世Web应用中实现安全、高效的身份验证。
2 用户mock和api

用户mock,user.js代码如下所示:
  1. const Mock = require('mockjs')
  2. const Random = Mock.Random
  3. module.exports = [
  4.   {
  5.     // 获取用户
  6.     url: '/api/user/info',
  7.     method: 'get',
  8.     response() {
  9.       return {
  10.         errno: 0,
  11.         data: {
  12.           username: Random.title(),
  13.           nickname: Random.cname(),
  14.         },
  15.       }
  16.     }
  17.   },
  18.   {
  19.     // 注册新用户
  20.     url: '/api/user/register',
  21.     method: 'post',
  22.     response() {
  23.       return {
  24.         errno: 0
  25.       }
  26.     }
  27.   },
  28.   {
  29.     // 用户登录
  30.     url: '/api/user/login',
  31.     method: 'post',
  32.     response() {
  33.       return {
  34.         errno: 0,
  35.         data: {
  36.           token: Random.word(20)
  37.         },
  38.       }
  39.     }
  40.   },
  41. ]
复制代码
前端user.ts 用户api接口代码如下所示:
  1. import request, { ResDataType } from "../services/request";
  2. /**
  3. * 获取用户信息
  4. * @returns  用户信息
  5. */
  6. export async function getUserInfoApi(): Promise<ResDataType> {
  7.   const url = "/api/user/info";
  8.   const data = (await request.get(url)) as ResDataType;
  9.   return data;
  10. }
  11. /**
  12. * 注册新用户
  13. * @returns  注册是否成功
  14. */
  15. export async function registerApi(
  16.   username: string,
  17.   password: string,
  18.   nickname?: string
  19. ): Promise<ResDataType> {
  20.   const url = "/api/user/register";
  21.   const body = { username, password, nickname: nickname || username };
  22.   const data = (await request.post(url, body)) as ResDataType;
  23.   return data;
  24. }
  25. /**
  26. * 用户登录
  27. * @returns  token
  28. */
  29. export async function loginApi(
  30.   username: string,
  31.   password: string
  32. ): Promise<ResDataType> {
  33.   const url = "/api/user/login";
  34.   const data = (await request.post(url, { username, password })) as ResDataType;
  35.   return data;
  36. }
复制代码
3 注册

Register.tsx代码如下所示:
  1. import { FC } from "react";
  2. import { Link, useNavigate } from "react-router-dom";
  3. import { Typography, Space, Form, Input, Button, message } from "antd";
  4. import { UserAddOutlined } from "@ant-design/icons";
  5. import { useRequest } from "ahooks";
  6. import { LOGIN_PATHNAME } from "../router";
  7. import { registerApi } from "@/api/user";
  8. import styles from "./Register.module.scss";
  9. const { Title } = Typography;
  10. const Register: FC = () => {
  11.   const nav = useNavigate();
  12.   const { run: handleRegister } = useRequest(
  13.     async (values) => {
  14.       const { username, password, nickname } = values;
  15.       return await registerApi(username, password, nickname);
  16.     },
  17.     {
  18.       manual: true,
  19.       onSuccess() {
  20.         message.success("注册成功");
  21.         // 跳转登录页
  22.         nav(LOGIN_PATHNAME);
  23.       },
  24.     }
  25.   );
  26.   function onFinish(values: any) {
  27.     handleRegister(values);
  28.   }
  29.   return (
  30.     <div className={styles.container}>
  31.       <div>
  32.         <Space>
  33.           <Title level={2}>
  34.             <UserAddOutlined />
  35.           </Title>
  36.           <Title level={2}>注册新用户</Title>
  37.         </Space>
  38.       </div>
  39.       <div>
  40.         <Form
  41.           labelCol={{ span: 6 }}
  42.           wrapperCol={{ span: 16 }}
  43.           onFinish={onFinish}
  44.         >
  45.           <Form.Item
  46.             label="用户名"
  47.             name="username"
  48.             rules={[
  49.               { required: true, message: "请输入用户名" },
  50.               {
  51.                 type: "string",
  52.                 min: 5,
  53.                 max: 20,
  54.                 message: "字符长度再5-20之间",
  55.               },
  56.               {
  57.                 pattern: /^\w+$/,
  58.                 message: "只能是字母数字下划线",
  59.               },
  60.             ]}
  61.           >
  62.             <Input />
  63.           </Form.Item>
  64.           <Form.Item
  65.             label="密码"
  66.             name="password"
  67.             rules={[
  68.               { required: true, message: "请输入用户名" },
  69.               {
  70.                 min: 8,
  71.                 message: "密码长度最少8位",
  72.               },
  73.             ]}
  74.           >
  75.             <Input.Password />
  76.           </Form.Item>
  77.           <Form.Item
  78.             label="确认密码"
  79.             name="confirm"
  80.             dependencies={["password"]}
  81.             rules={[
  82.               {
  83.                 required: true,
  84.                 message: "请输入确认密码",
  85.               },
  86.               ({ getFieldValue }) => ({
  87.                 validator(_, value) {
  88.                   if (!value || getFieldValue("password") === value) {
  89.                     return Promise.resolve();
  90.                   } else {
  91.                     return Promise.reject(new Error("两次密码不一致"));
  92.                   }
  93.                 },
  94.               }),
  95.             ]}
  96.           >
  97.             <Input.Password />
  98.           </Form.Item>
  99.           <Form.Item label="昵称" name="nickname">
  100.             <Input />
  101.           </Form.Item>
  102.           <Form.Item wrapperCol={{ offset: 6, span: 16 }}>
  103.             <Space>
  104.               <Button type="primary" htmlType="submit">
  105.                 注册
  106.               </Button>
  107.               <Link to={LOGIN_PATHNAME}>已有账户,登录</Link>
  108.             </Space>
  109.           </Form.Item>
  110.         </Form>
  111.       </div>
  112.     </div>
  113.   );
  114. };
  115. export default Register;
复制代码
执行注册,乐成寻衅登录页,如下图所示:

4 登录

登录页Login.tsx代码如下所示:
  1. import { FC, useEffect } from "react";
  2. import { Link, useNavigate } from "react-router-dom";
  3. import { Typography, Space, Form, Input, Button, Checkbox, message } from "antd";
  4. import { UserAddOutlined } from "@ant-design/icons";
  5. import { useRequest } from "ahooks";
  6. import { MANAGE_INDEX_PATHNAME, REGISTER_PATHNAME } from "../router";
  7. import { loginApi } from "@/api/user";
  8. import styles from "./Register.module.scss";
  9. const { Title } = Typography;
  10. const USERNAME_KEY = "username";
  11. const PASSWORD_KEY = "password";
  12. /**
  13. * 浏览器本地存储用户信息
  14. * @param username 用户名
  15. * @param password 密码
  16. */
  17. function rememberUser(username: string, password: string) {
  18.   localStorage.setItem(USERNAME_KEY, username);
  19.   localStorage.setItem(PASSWORD_KEY, password);
  20. }
  21. /**
  22. * 浏览器本地删除用户信息
  23. * @param username 用户名
  24. * @param password 密码
  25. */
  26. function deleteUserFromStorage(username: string, password: string) {
  27.   localStorage.removeItem(USERNAME_KEY);
  28.   localStorage.removeItem(PASSWORD_KEY);
  29. }
  30. /**
  31. * 浏览器本地获取用户信息
  32. */
  33. function getUserInfoFromStorage() {
  34.   return {
  35.     username: localStorage.getItem(USERNAME_KEY),
  36.     password: localStorage.getItem(PASSWORD_KEY),
  37.   };
  38. }
  39. const Login: FC = () => {
  40.   const nav = useNavigate()
  41.   // 表单组件初始化
  42.   const [form] = Form.useForm();
  43.   useEffect(() => {
  44.     const { username, password } = getUserInfoFromStorage();
  45.     form.setFieldsValue({ username, password });
  46.     // eslint-disable-next-line react-hooks/exhaustive-deps
  47.   }, []);
  48.   const { run: handleLogin } = useRequest(
  49.     async (values) => {
  50.       const { username, password } = values;
  51.       return await loginApi(username, password);
  52.     },
  53.     {
  54.       manual: true,
  55.       onSuccess(res) {
  56.         message.success("登录成功")
  57.         // todo 存储token
  58.         // 跳转我的问卷
  59.         nav(MANAGE_INDEX_PATHNAME)
  60.       },
  61.     }
  62.   );
  63.   function onFinish(values: any) {
  64.     const { username, password, remember } = values || {};
  65.     if (remember) {
  66.       rememberUser(username, password);
  67.     } else {
  68.       deleteUserFromStorage(username, password);
  69.     }
  70.     handleLogin({ username, password });
  71.   }
  72.   return (
  73.     <div className={styles.container}>
  74.       <div>
  75.         <Space>
  76.           <Title level={2}>
  77.             <UserAddOutlined />
  78.           </Title>
  79.           <Title level={2}>用户登录</Title>
  80.         </Space>
  81.       </div>
  82.       <div>
  83.         <Form
  84.           labelCol={{ span: 6 }}
  85.           wrapperCol={{ span: 16 }}
  86.           onFinish={onFinish}
  87.           initialValues={{ remember: true }}
  88.           form={form}
  89.         >
  90.           <Form.Item
  91.             label="用户名"
  92.             name="username"
  93.             rules={[
  94.               { required: true, message: "请输入用户名" },
  95.               {
  96.                 type: "string",
  97.                 min: 5,
  98.                 max: 20,
  99.                 message: "字符长度再5-20之间",
  100.               },
  101.               {
  102.                 pattern: /^\w+$/,
  103.                 message: "只能是字母数字下划线",
  104.               },
  105.             ]}
  106.           >
  107.             <Input />
  108.           </Form.Item>
  109.           <Form.Item
  110.             label="密码"
  111.             name="password"
  112.             rules={[
  113.               { required: true, message: "请输入用户名" },
  114.               {
  115.                 min: 8,
  116.                 message: "密码长度最少8位",
  117.               },
  118.             ]}
  119.           >
  120.             <Input.Password />
  121.           </Form.Item>
  122.           <Form.Item
  123.             wrapperCol={{ offset: 6, span: 16 }}
  124.             name="remember"
  125.             valuePropName="checked"
  126.           >
  127.             <Checkbox>记住我</Checkbox>
  128.           </Form.Item>
  129.           <Form.Item wrapperCol={{ offset: 6, span: 16 }}>
  130.             <Space>
  131.               <Button type="primary" htmlType="submit">
  132.                 登录
  133.               </Button>
  134.               <Link to={REGISTER_PATHNAME}>注册新用户</Link>
  135.             </Space>
  136.           </Form.Item>
  137.         </Form>
  138.       </div>
  139.     </div>
  140.   );
  141. };
  142. export default Login;
复制代码
登录乐成后跳转我的问卷也,如下图所示:

5 token存储

用户登录乐成后,必要存储token,userToken.ts代码如下所示
  1. /**
  2. * @description localStorage管理用户token
  3. * @author gaogzhen
  4. */
  5. const KEY = "USER-TOKEN"
  6. /**
  7. * 设置token
  8. * @param token
  9. */
  10. export function setToken(token:string) {
  11.   localStorage.setItem(KEY, token)  
  12. }
  13. /**
  14. * 获取token
  15. * @returns token
  16. */
  17. export function getToken() {
  18.   return localStorage.getItem(KEY) || ''
  19. }
  20. /**
  21. * 删除token
  22. */
  23. export function removeToken() {
  24.   localStorage.removeItem(KEY)
  25. }
复制代码
登录页登录乐成后,执行存储token,Login.tsx代码如下:
  1.   const { run: handleLogin } = useRequest(
  2.     async (values) => {
  3.       const { username, password } = values;
  4.       return await loginApi(username, password);
  5.     },
  6.     {
  7.       manual: true,
  8.       onSuccess(res) {
  9.         message.success("登录成功");
  10.         // 存储token
  11.         const { token = "" } = res;
  12.         setToken(token);
  13.         // 跳转我的问卷
  14.         nav(MANAGE_INDEX_PATHNAME);
  15.       },
  16.     }
  17.   );
复制代码
localStorage存储如下图哦所示:

6 哀求拦截器设置token

登录乐成后,用户每次哀求必要携带token,用户身份验证、权限验证等。这里通过哀求拦截器实现,request.ts代码如下所示:
  1. import axios from "axios";
  2. import { message } from "antd";
  3. import { AUTHORIZATION } from "@/constant";
  4. import { getToken } from "@/utils/userToken";
  5. const request = axios.create({
  6.   timeout: 5000,
  7. });
  8. // request拦截:每次请求携带token
  9. request.interceptors.request.use((config) => {
  10.   // todo token 校验
  11.   config.headers[AUTHORIZATION] = `Bearer ${getToken()}`;
  12.   return config;
  13. });
  14. // response 拦截:统一处理errno和msg
  15. request.interceptors.response.use((res) => {
  16.   const resData = (res.data || {}) as ResType;
  17.   const { errno, data, msg } = resData;
  18.   if (errno !== 0) {
  19.     // 错误提示
  20.     if (msg) {
  21.       message.error(msg);
  22.     }
  23.     throw new Error(msg);
  24.   }
  25.   return data as any;
  26. });
  27. export default request;
  28. export type ResDataType = {
  29.   [key: string]: any;
  30. };
  31. export type ResType = {
  32.   errno: number;
  33.   data?: ResDataType;
  34.   msg?: string;
  35. };
复制代码
结果如下图所示:

6 获取用户信息

用户登录之后,用户信息很多地方必要利用,在学习状态管理之后再处置惩罚,这里我们暂时在用户信息组件处置惩罚。
用户信息UserInfo.tsx代码如下所示:
  1. import { FC } from "react";
  2. import { Link } from "react-router-dom";
  3. import { LOGIN_PATHNAME } from "../router/index";
  4. import { useRequest } from "ahooks";
  5. import { getUserInfoApi } from "@/api/user";
  6. import { UserOutlined } from "@ant-design/icons";
  7. import { Button } from "antd";
  8. const UserInfo: FC = () => {
  9.   const { data } = useRequest(getUserInfoApi);
  10.   const { username, nickname } = data || {};
  11.   const User = (
  12.     <>
  13.       <span style={{ color: "#e8e8e8" }}>
  14.         <UserOutlined />
  15.         {nickname}
  16.       </span>
  17.       <Button type="link">退出</Button>
  18.     </>
  19.   );
  20.   const Login = <Link to={LOGIN_PATHNAME}>登录</Link>;
  21.   return <>{username ? User : Login}</>;
  22. };
  23. export default UserInfo;
复制代码
结果如下图所示:

7 退出登录

UserInfo.tsx退出功能代码如下所示:
  1. import { FC } from "react";
  2. import { Link, useNavigate } from "react-router-dom";
  3. import { useRequest } from "ahooks";
  4. import { Button } from "antd";
  5. import { UserOutlined } from "@ant-design/icons";
  6. import { LOGIN_PATHNAME } from "../router/index";
  7. import { getUserInfoApi } from "@/api/user";
  8. import { removeToken } from "@/utils/userToken";
  9. const UserInfo: FC = () => {
  10.   const nav = useNavigate()
  11.   const { data } = useRequest(getUserInfoApi);
  12.   const { username, nickname } = data || {};
  13.   function logout() {
  14.     removeToken()
  15.     // 跳转登录页
  16.     nav(LOGIN_PATHNAME)
  17.   }
  18.   const User = (
  19.     <>
  20.       <span style={{ color: "#e8e8e8" }}>
  21.         <UserOutlined />
  22.         {nickname}
  23.       </span>
  24.       <Button type="link" onClick={logout}>退出</Button>
  25.     </>
  26.   );
  27.   const Login = <Link to={LOGIN_PATHNAME}>登录</Link>;
  28.   return <>{username ? User : Login}</>;
  29. };
  30. 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].

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
继续阅读请点击广告

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

×
登录参与点评抽奖,加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表