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

Posted 橘猫吃不胖~

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 

以上是关于token验证概述及在React中实现token验证的主要内容,如果未能解决你的问题,请参考以下文章

Vue项目中实现用户登录及token验证

Vue项目中实现用户登录及token验证

Vue项目中实现用户登录及token验证

Vue中实现token验证

Vue中实现token验证

使用 Microsoft System.IdentityModel.Tokens.Jwt 在 Asp.net WebApi 中实现 JWT 身份验证