IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> token验证概述及在React中实现token验证 -> 正文阅读

[JavaScript知识库]token验证概述及在React中实现token验证

token验证概述及在React中实现token验证

1 什么是token

token的意思是“令牌”,是服务端生成的一串字符串作为客户端进行请求的一个标识。当用户第一次登录后,服务器生成一个token并将此token返回给客户端,
以后客户端只需带上这个token前来请求数据即可,无需再次带上用户名和密码。

简单token的组成:①uid(用户唯一的身份标识);②time(当前时间的时间戳);③sign(签名,token的前几位以哈希算法压缩成的一定长度的十六进制字符串。为防止token泄露)。

页面请求的控制:
①第一次登录时,需要用户名和密码。登录成功后服务器端会生成token标识,该标识返回给客户端
②当用户再次发起请求时,只需要带上token标识即可,不需要用户名和密码

2 token的作用

  1. 防止表单重复提交。主要的理念是,客户端初始化的时候(一般就是刚刚进入页面的时候)就调用后端代码,后端代码生成一个token,返回给客户端,客户端储存token(可以在前台使用Form表单中使用隐藏域来存储这个Token,也可以使用cookie),然后就将request(请求)中的token 与(session)中的token进行比较
  2. 用来作身份验证
    (1)身份认证概述
    由于HTTP是一种没有状态的协议,它并不知道是谁访问了我们的应用。这里把用户看成是客户端,客户端使用用户名还有密码通过了身份验证,不过下次这个客户端再发送请求时候,还得再验证一下。
    ???????通用的解决方法是:当用户请求登录的时候,如果没有问题,在服务端生成一条记录,在这个记录里可以说明登录的用户是谁,然后把这条记录的id 发送给客户端,客户端收到以后把这个id存储在cookie里,下次该用户再次向服务端发送请求的时候,可以带上这个cookie,这样服务端会验证一下cookie里的信息,看能不能在服务端这里找到对应的记录,如果可以,说明用户已经通过了身份验证,就把用户请求的数据返回给客户端。以上所描述的过程就是利用session,那个id值就是sessionid。我们需要在服务端存储为用户生成的session,这些session 会存储在内存,磁盘,或者数据库。

    (2)基于token机制的身份认证:使用token机制的身份验证方法,在服务器端不需要存储用户的登录记录。流程如下:
    A. 客户端使用用户名和密码请求登录。
    B. 服务端收到请求,验证用户名和密码。
    ???????a. 在数据库中存放用户的信息(用户名、密码)
    ???????b. 使用客户端提交的用户名、密码查询数据,若查询到了结果则表示是合法的用户,若没有查询到结果则是非法用户
    C. 验证成功后,服务端会生成一个token,然后把这个token 发送给客户端。
    D. 客户端收到token后把它存储起来,可以放在cookie或者Local Storage(本地存储)里。
    E. 客户端每次向服务端发送请求的时候都需要带上服务端发给的token。
    F. 服务端收到请求,然后去验证客户端请求里面带着token,如果验证成功,就向客户端返回请求的数据。

3 React中实现token验证

接下来使用登录和注册的案例来说明。创建Express项目server1,在当前项目下实现后端;创建React项目demo1,在当前项目下配置前端。

数据库说明

本案例使用stu数据库中的admin表来存放用户名以及密码,stu表结构如下:
在这里插入图片描述

后端说明

首先安装一些模块:

# 跨域模块
npm install cors
# crypto模块,是一个加密模块,使用摘要算法(MD5)进行加密
npm install crypto
# jsonwebtoken模块:生成token信息
npm install jsonwebtoken

1、配置数据库。在当前项目下新建config文件夹,创建dbconfig.js文件,该文件用来连接mysql数据库,具体代码如下:

var Sequelize = require("sequelize");
// 参数分别是:数据库名、用户名、密码
const DB = new Sequelize("info", "root", "123456", {
    host: "localhost", // 主机地址
    port: 3306, // 数据库端口号
    dialect: "mysql", // 数据库类型
    pool: { // 数据库连接池
        max: 5, // 最大连接数量
        min: 0, // 最小连接数量
        idle: 10000, // 如果10秒内没有被使用,释放该线程
    }
})

module.exports = DB;

2、创建模型。在当前项目下新建文件夹Model,创建adminModel.js文件,该文件用来将数据库中的表映射到对象中,具体代码如下:

const DB = require("../config/dbconfig");
const Sequelize = require("sequelize");

const adminModel = DB.define("admin", {
    id: {
        type: Sequelize.INTEGER,
        primaryKey: true,
        autoIncrement: true,
        unique: true
    },
    username: {
        type: Sequelize.STRING(30),
        allowNull: false
    },
    password: {
        type: Sequelize.STRING(255),
        allowNull: false
    }
}, {
    freezeTableName: true,
    timestamps: false
})

module.exports = adminModel;

3、在bin文件夹下的www.js文件中修改端口号为8089,具体为:

// 原代码
var port = normalizePort(process.env.PORT || '3000');
// 修改端口号后
var port = normalizePort(process.env.PORT || '8089');

4、在routes文件夹下新建文件admin.js,在该文件中写注册和登录的接口,具体代码如下:

var express = require("express");
var router = express.Router(); // 使用路由模块化管理
var crypto = require("crypto"); // 导入加密模块
var jwt = require("jsonwebtoken");
var adminModel = require("../Model/adminModel"); // 导入数据库模型

/*
* 注册:http://localhost:8089/admin/register
* 步骤如下:
* (1)获取用户的用户名和密码
* (2)创建MD5摘要算法的对象,利用该对象对密码进行加密
* (3)将加密后的密码保存到数据库中
*/
router.post("/register", (req, res) => {
    let username = req.body.username; // 获取用户名
    let password = req.body.password; // 获取密码
    // 创建MD5对象
    let md5 = crypto.createHash("md5");
    // 对密码进行加密,"hex"表示密码是十六进制的字符串
    let newPwd = md5.update(password).digest("hex");
    // 将用户名和密码保存到数据库中
    adminModel.create({
        username: username,
        password: newPwd
    }).then(result => { // 创建成功后传递的数据
        res.json({
            code: 1000,
            msg: "注册成功"
        })
    }).catch(err => {
        console.log(err);
        res.json({
            code: 1002,
            msg: "注册失败"
        })
    })
})

/*
* 登录:http://localhost:8089/admin/login
* 步骤如下:
* (1)获取用户输入的用户名和密码
* (2)使用MD5加密用户输入的密码
* (3)将用户名与加密后的密码与数据库中的用户名和密码进行对比
* (4)对比成功,则是合法用户,生成token,然后将token和其他的信息一起打包给客户端
* (5)对比不成功,非法用户,不生成token,相应给客户端的信息不包含token
*/
router.post("/login", (req, res) => {
    // 在服务器端以对象的方式将用户名和密码接收
    let user = req.body.user;
    // 获取对象中的用户名和密码
    let username = user.username;
    let password = user.password;
    // 创建MD5对象
    let mds = crypto.createHash("md5");
    // 对密码进行加密,密码是十六进制的字符串
    let newPwd = mds.update(password).digest("hex");
    // 查询
    adminModel.findAll({
        where: { // 查找用户名
            username: username
        }
    }).then(data => { // 用户名可能相同,因此可能查找多条记录,这时data是一个数组,因此在设计注册时,必须保证用户名不重复
        if (data.length !== 0) { // 查询到了数据,用户名存在
            if (data[0].password == newPwd) { // 密码相同
                // 合法用户,生成token
                // jwt.sign()传入要生成token信息的对象,其中的"jmcbp"可以让token信息更加难以破解
                let newToken = jwt.sign({...data[0]}, "jmcbp", {
                    expiresIn: 1440 // token的过期时间
                })
                // 将token和其他信息打包后相应给客户端
                res.json({
                    code: 1000,
                    msg: "登录成功",
                    token: newToken
                })
            } else { // 密码不相同
                res.json({
                    code: 1002,
                    msg: "密码错误"
                })
            }
        } else { // 没有查询到数据
            res.json({
                code: 1002,
                msg: "用户不存在"
            })
        }
    })
})

module.exports = router;

5、在app.js文件中配置跨域模块和路由模块:

// 跨域
var cors = require("cors");
app.use(cors());
// 路由
var adminRouter = require('./routes/admin');
app.use('/admin', adminRouter);

在Postman中测试这两个接口,首先是注册接口:
在这里插入图片描述
登录接口,由于登录接口接收的参数是对象形式的,因此使用JSON格式传递参数,首先在Headers中设置Content-Type为application/json:
请添加图片描述
然后选择Body,选择raw,修改JSON,传入参数即可
请添加图片描述

前端说明

首先安装模块

npm install react-router-dom@5.2.0
npm install antd

1、在src文件夹下新建文件夹components,在该文件夹下新建文件admin.js,在该文件中使用antd制作一个简易的登录和注册的界面,具体代码如下:

import React from "react";
import {Form, Row, Button, Input, message} from "antd";
import axios from "axios";

class Login extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            username: " ",
            password: " "
        }
    }

    loginFormRef = React.createRef(); // 与render函数中的登录Form组件进行绑定
    regFormRef = React.createRef(); // 与render函数中的注册Form绑定

    login = async () => { // 点击登录后执行的函数
        // 对表单控件进行规则验证:验证设置了rules属性的控件
        await this.loginFormRef.current.validateFields().then(value => {
            // 验证成功
            // value存放了通过验证的控件的值
            this.setState({ // 将用户名和密码更新为输入的值
                username: value.username,
                password: value.password
            })
            // 向服务器发起登录请求
            axios.post("http://localhost:8089/admin/login", {user: value})
                .then(result => {
                    if (result.data.code == 1000) { // 登录成功
                        // 将用户名写入sessionStorage中,这可以制作用户名显示在页面上的效果
                        sessionStorage.setItem("username", value.username);
                        // 将token写入
                        sessionStorage.setItem("token", result.data.token);
                        // 进行页面的跳转,/home是自定义的跳转路由,在本例中不定义
                        this.props.history.push("/home");
                    } else { // 登录失败
                        message.error(result.data.msg);
                        return;
                    }
                }).catch(err => {
                console.log(err);
            })
        })
    }

    register = async () => {
        await this.regFormRef.current.validateFields().then(value => {
            axios.post("http://localhost:8089/admin/register", value)
                .then(result => { // 注册成功
                    // 给出消息提示
                    message.success(result.data.msg);
                    // 接下来可以让页面跳转到登录页面,由于本案例两个页面在一起,因此这句话不使用
                    // this.props.history.push("/");
                }).catch(err => {
                console.log(err);
            })
        })
    }

    render() {
        return (
            <div>
                <div style={{margin: "10px 20px", float: "left"}}>
                    <Form ref={this.loginFormRef}>
                        <Row>
                            <h2>用户登录</h2>
                        </Row>
                        <Row>
                            <Form.Item label={"用户名:"} name={"username"} rules={[
                                {
                                    required: true,
                                    message: "用户名不能为空"
                                }
                            ]}>
                                <Input placeholder={"用户名"}/>
                            </Form.Item>
                        </Row>
                        <Row>
                            <Form.Item label="密&nbsp;&nbsp;&nbsp;码:" name={"password"} rules={[
                                {
                                    required: true,
                                    message: "密码不能为空"
                                }
                            ]}>
                                <Input.Password placeholder={"密码"}/>
                            </Form.Item>
                        </Row>
                        <Row>
                            <Button type={"primary"} onClick={this.login}>登录</Button>
                        </Row>
                    </Form>
                </div>
                <div style={{margin: "10px 20px", float: "left"}}>
                    <Form ref={this.regFormRef}>
                        <Row>
                            <h2>用户注册</h2>
                        </Row>
                        <Row>
                            <Form.Item label={"用户名:"} name={"username"} rules={[
                                {
                                    required: true,
                                    message: "用户名不能为空"
                                }
                            ]}>
                                <Input placeholder={"用户名"}/>
                            </Form.Item>
                        </Row>
                        <Row>
                            <Form.Item label="密&nbsp;&nbsp;&nbsp;码:" name={"password"} rules={[
                                {
                                    required: true,
                                    message: "密码不能为空"
                                }
                            ]}>
                                <Input.Password placeholder={"密码"}/>
                            </Form.Item>
                        </Row>
                        <Row>
                            <Form.Item label={"确认密码:"} name={"confirmPwd"} rules={[
                                { // 两个验证要求,因此两个对象
                                    required: true,
                                    message: "确认密码不能为空"
                                },
                                {
                                    validator: (rules, value) => {
                                        // 获取用户输入的密码
                                        let pwd = this.regFormRef.current.getFieldValue("password");
                                        if (pwd && pwd !== value) {
                                            // value表示当前控件的值
                                            return Promise.reject("两次密码不一致");
                                        } else {
                                            return Promise.resolve(); // 表示验证通过
                                        }
                                    }
                                }
                            ]}>
                                <Input.Password placeholder={"密码"}/>
                            </Form.Item>
                        </Row>
                        <Row>
                            <Button type={"primary"} onClick={this.register}>注册</Button>
                        </Row>
                    </Form>
                </div>
            </div>
        )
    }
}

export default Login;

在这里插入图片描述
2、上面可以实现在登录后跳转到主页面的效果,但是当用户直接输入主页面的路由地址,就会跳过登录过程直接进入主页面,因此我们还需要自定义路由组件,判断用户是否登录。在src目录下新建文件夹routers,并在其中新建文件privateRouter.js,具体代码如下:

import {Route, Redirect} from "react-router-dom";
import {Component} from "react";
// 定义鉴权函数
let authenticate = () => {
    // 获取页面中存储的token
    let token = sessionStorage.getItem('token');
    // 根据是否存在token,返回不同的值
    return token ? true : false
}

// 定义路由组件
// ...rest将传给组件的所有参数全部解析出来
const PrivateRoute = ({component: Component, ...rest}) => {
    return (
        <Route
            {...rest}
            // 进行重新渲染
            render={(props) => authenticate() ? <Component {...props}/> :
                <Redirect to={{
                    pathname: '/', // 指定重定向的路径
                    state: {from: props.location} // 将重定向的路径放入页面的地址栏
                }}/>
            }
        ></Route>
    )
}
export default PrivateRoute;

3、最后一步:在App.js文件中进行路由配置,代码如下:

import './App.css';
import Login from "./components/login";
import {BrowserRouter, Route} from "react-router-dom";
import PrivateRoute from "./routes/privateRoute";

function App() {
    return (
        <div className="App">
            <BrowserRouter>
                <Route exact path="/" component={Login}/>
                <PrivateRoute path={"/home"} component={未定义的}/>
            </BrowserRouter>
        </div>
    );
}

export default App;

这样设置后,如果用户直接输入首页的地址,就会跳转回登录页面。

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-03-16 22:12:46  更:2022-03-16 22:16:14 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 6:49:33-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码