使用jsonwebtoken进行node端登陆注册验证

简介

简介:Token在计算机身份认证中是令牌(临时)的意思,在词法分析中是标记的意思。一般作为邀请、登录系统使用。

Token 是在服务端产生的。如果前端使用用户名/密码向服务端请求认证,服务端认证成功,那么在服务端会返回 Token 给前端。前端可以在每次请求的时候带上 Token 证明自己的合法地位。如果这个 Token 在服务端持久化(比如存入数据库),那它就是一个永久的身份令牌(参考资料

为什么使用它

token解决的问题如下

  1. Token 完全由应用管理,所以它可以避开同源策略
  2. Token 可以避免 CSRF 攻击(又引出一个知识点,推荐文章前端知识真是如同太平洋广,如同马里亚纳海沟深
  3. Token 可以是无状态的,可以在多个服务间共享

其它(cookie、session、token)

本实验主要采用token进行前后端验证,但是同时也有必要了解其他验证方式的原理,推荐看看下面的文章

博客链接:🔺🔺🔺前端鉴权的兄弟们:cookie、session、token、jwt、单点登录 - 掘金 (juejin.cn),通过该博客,你可了解到

  1. 基于 HTTP 的前端鉴权背景
  2. cookie 为什么是最方便的存储方案,有哪些操作 cookie 的方式
  3. session 方案是如何实现的,存在哪些问题
  4. token 方案是如何实现的,如何进行编码和防篡改?jwt 是做什么的?refresh token 的实现和意义
  5. session 和 token 有什么异同和优缺点
  6. 单点登录是什么?实现思路和在浏览器下的处理

代码

参考视频(强烈建议看,逻辑清晰,几乎不带一句废话,虽然有点长):1小时搞定NodeJs(Express)的用户注册、登录和授权_哔哩哔哩_bilibili🔺🔺🔺

步骤(该案例模拟的用户名为唯一值unique)

  1. 相关模块以及初始化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const express = require('express'); //expree服务器
    const bcrypt = require('bcrypt'); //加密
    const jwt = require('jsonwebtoken'); //token
    const User = require('./User.js'); //用户模块


    const app = express();
    // 定义密钥进行token加密,也可通过openssl进行私钥公钥配对,后面有解释
    let SECRET = 'dongyuan666';
    // 接收前端传递的json数据
    app.use(express.json());
  2. 用户名唯一,密码加密(User模块)

    • bcrypt模块 -> set函数 -> bcrypt.hashSync(val, number)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    const UserSchema = new mongoose.Schema({
    username: {
    type: String,
    unique: true //唯一值
    },
    password: {
    type: String,
    set: (val) => {
    return bcrypt.hashSync(val, 10) //加密
    }
    },
    });

    User完整代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    const mongoose = require('mongoose');
    const bcrypt = require('bcrypt')

    mongoose.connect('mongodb://localhost:27017/test-token', {
    useNewUrlParser: true,
    useCreateIndex: true,
    });

    const UserSchema = new mongoose.Schema({
    username: {
    type: String,
    unique: true
    },
    password: {
    type: String,
    set: (val) => {
    return bcrypt.hashSync(val, 10) //加密
    }
    },
    });

    module.exports = mongoose.model('User', UserSchema)
  3. 处理用户注册

    1. 判断当前用户是否已被注册:User.find({ username: req.body.username,})
    2. 写入数据库:User.create({username: req.body.username,password: req.body.password,})
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    app.post('/register', async (req, res) => {
    // console.log(req.body);

    let users = await User.find({
    username: req.body.username,
    });
    if (users.length != 0) return res.end('该用户已注册');

    await User.create({
    username: req.body.username,
    password: req.body.password,
    });
    res.end('注册成功');
    });
  4. 处理用户登录

    1. 判断数据库是否有当前用户:User.findOne({ username: body.username })
    2. 判断密码是否正确:bcrypt.compareSync(body.password, user.password);
    3. 生成token:jwt.sign({id}, SECRET, {options})
      • 定义SECRET:let SECRET = '自定义密钥内容';
    4. 发送token
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    let SECRET = '自定义密钥内容';

    app.post('/login', async (req, res) => {
    let body = req.body;

    //1.判断用户是否存在
    let user = await User.findOne({ username: body.username });
    if (!user) return res.status(422).json({ errorCode: 1, message: '用户不存在' });

    // 用户存在
    // 2.密码校验
    let isPasswordValid = bcrypt.compareSync(body.password, user.password);
    if (!isPasswordValid) return res.status(422).json({ errorCode: 2, message: '密码错误' });

    // 3.生成token
    let token = jwt.sign(
    { id: String(user.id) }, //密码不要放进来,放一个唯一的东西就可以了
    SECRET, //密钥
    {
    expiresIn: 60 * 60 * 24, //24h后失效
    } //配置项
    );
    res.send(token);
    });
  5. 处理用户验证

    1. 获取token:req.headers.authorization
    2. 解析token:jwt.verify(raw, SECRET)
    3. 查找用户:User.findById(id)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    app.get('/profile', async (req, res) => {
    // 获取token
    let raw = String(req.headers.authorization).split(' ')[1];
    // 解析token并获取id
    let id = jwt.verify(raw, SECRET).id;
    // 查找用户
    let user = await User.findById(id);
    res.send(user);
    });
  6. 将token验证过程拆解成中间件,并进行验证

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 中间件
    async function auth(req, res, next) {
    // 获取token
    let raw = String(req.headers.authorization).split(' ')[1];
    // 解析token并获取id
    let id = jwt.verify(raw, SECRET).id;
    // 查找用户
    req.user = await User.findById(id);
    next();
    }

    app.get('/market', auth, async (req, res, next) => {
    res.send(req.user);
    });

    完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
const express = require('express');
const bcrypt = require('bcrypt'); //加密
const jwt = require('jsonwebtoken'); //token
const User = require('./User.js'); //用户模块

const app = express();
// 定义密钥,也可通过openssl进行私钥公钥配对
let SECRET = 'dongyuan666';

// 接收前端传递的json数据
app.use(express.json());

app.get('/', (req, res) => {
console.log(req.query);
res.send('Ok');
});

app.post('/register', async (req, res) => {
// console.log(req.body);

let users = await User.find({
username: req.body.username,
});
if (users.length != 0) return res.end('该用户已注册');

await User.create({
username: req.body.username,
password: req.body.password,
});
res.end('注册成功');
});

app.post('/login', async (req, res) => {
let body = req.body;

//1.判断用户是否存在
let user = await User.findOne({ username: body.username });
if (!user) return res.status(422).json({ errorCode: 1, message: '用户不存在' });

// 用户存在
// 2.密码校验
let isPasswordValid = bcrypt.compareSync(body.password, user.password);
if (!isPasswordValid) return res.status(422).json({ errorCode: 2, message: '密码错误' });

// 3.生成token
let token = jwt.sign(
{ id: String(user.id) }, //密码不要放进来,放一个唯一的东西就可以了
SECRET, //密钥
{
expiresIn: 60 * 60 * 24, //24h后失效
} //配置项
);
res.send(token);
});

app.get('/profile', async (req, res) => {
// return res.send(String(req.headers.authorization).split(' '))

let raw = String(req.headers.authorization).split(' ')[1];
let id = jwt.verify(raw, SECRET).id;
let user = await User.findById(id);
res.send(user);
});

// 中间件
async function auth(req, res, next) {
// 获取token
let raw = String(req.headers.authorization).split(' ')[1];
// 解析token并获取id
let id = jwt.verify(raw, SECRET).id;
// 查找用户
req.user = await User.findById(id);
next();
}

app.get('/market', auth, async (req, res, next) => {
res.send(req.user);
});

app.listen(3000, () => {
console.log('server is running');
});

🔺非对称加密

在Node.Js中使用JWT实现Token用户验证🔺🔺🔺 or node token验证

非对称加密:RS256

私钥(private key):用于发布令牌

公钥(public key):用于验证令牌

非对称加密步骤

  1. 使用openssl生成公钥私钥

    1
    2
    3
    openssl
    OpenSSL> genrsa -out private.key 1024
    OpenSSL> rsa -in private.key -pubout -out public.key

    image-20220328102415279

  2. 新建一个文件用于读取密钥并导出(config.js)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const fs = require('fs');
    const path = require('path');

    const PRIVATE_KEY = fs.readFileSync(path.resolve(__dirname, './keys/private.key'));
    const PUBLIC_KEY = fs.readFileSync(path.resolve(__dirname, './keys/public.key'));

    module.exports.PRIVATE_KEY = PRIVATE_KEY;
    module.exports.PUBLIC_KEY = PUBLIC_KEY;

  3. 导入密钥(app.js)

    1
    const { PRIVATE_KEY, PUBLIC_KEY } = require('./config.js');
  4. 使用私钥进行加密

    1
    2
    3
    4
    5
    6
    7
    8
    let token = jwt.sign(
    { id: String(user.id) }, //密码不要放进来,放一个唯一的东西就可以了
    PRIVATE_KEY, //私钥加密
    {
    expiresIn: 60 * 60 * 24, //24h后失效
    algorithm: 'RS256', //非对称加密
    } //配置项
    );
  5. 使用公钥进行解密

    1
    jwt.verify(raw, PUBLIC_KEY, { algorithms: ['RS256'] })

    Token安全之道

建议阅读:token拷贝到别人电脑上,禁止授权🔺

简短:

  1. 关于 token 被盗取的问题
  2. 如果一个用户的 token 被其他用户劫持了,怎样解决这个安全问题

实践:注册、登录和 token 的安全之道