token验证概述及在React中实现token验证
1 什么是token
token的意思是“令牌”,是服务端生成的一串字符串,作为客户端进行请求的一个标识。当用户第一次登录后,服务器生成一个token并将此token返回给客户端, 以后客户端只需带上这个token前来请求数据即可,无需再次带上用户名和密码。
简单token的组成:①uid(用户唯一的身份标识);②time(当前时间的时间戳);③sign(签名,token的前几位以哈希算法压缩成的一定长度的十六进制字符串。为防止token泄露)。
页面请求的控制: ①第一次登录时,需要用户名和密码。登录成功后服务器端会生成token标识,该标识返回给客户端 ②当用户再次发起请求时,只需要带上token标识即可,不需要用户名和密码
2 token的作用
- 防止表单重复提交。主要的理念是,客户端初始化的时候(一般就是刚刚进入页面的时候)就调用后端代码,后端代码生成一个token,返回给客户端,客户端储存token(可以在前台使用Form表单中使用隐藏域来存储这个Token,也可以使用cookie),然后就将request(请求)中的token 与(session)中的token进行比较
- 用来作身份验证。
(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
npm install crypto
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,
}
})
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");
router.post("/register", (req, res) => {
let username = req.body.username;
let password = req.body.password;
let md5 = crypto.createHash("md5");
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: "注册失败"
})
})
})
router.post("/login", (req, res) => {
let user = req.body.user;
let username = user.username;
let password = user.password;
let mds = crypto.createHash("md5");
let newPwd = mds.update(password).digest("hex");
adminModel.findAll({
where: {
username: username
}
}).then(data => {
if (data.length !== 0) {
if (data[0].password == newPwd) {
let newToken = jwt.sign({...data[0]}, "jmcbp", {
expiresIn: 1440
})
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();
regFormRef = React.createRef();
login = async () => {
await this.loginFormRef.current.validateFields().then(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.setItem("username", value.username);
sessionStorage.setItem("token", result.data.token);
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);
}).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="密 码:" 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="密 码:" 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) {
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 = () => {
let token = sessionStorage.getItem('token');
return token ? true : false
}
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;
这样设置后,如果用户直接输入首页的地址,就会跳转回登录页面。
|