nodejs学习笔记 | 前后端身份认证

702 阅读6分钟

| 什么是身份认证

身份认证Authentication又叫鉴权,指通过一定手段完成对用户的身份确认

  • 日常身份认证:微信支付、高铁验票

  • web中的手机验证码登录、邮箱密码登录、二维码登录等

由于HTTP协议是无状态性的,客户端的每次HTTP请求都是独立,连续多个请求之间没有直接的关系,服务器并不会保留每次请求状态

因此为了突破HTTP无状态限制,需要身份认证
说到身份认证就不得不提一下CookieSession

Cookie

Cookie是存储在用户浏览器中的一段不超过4KB的字符串
由一个名称、值、和有效期、安全性、使用范围的可选属性组成
不同域名下Cookie各自独立,每当客户端发起请求时,会把当前域名下未过期的Cookie一同发送到服务器

特性

  1. 自动发送
  2. 域名独立
  3. 过期时限
  4. 4KB限制

Cookie在身份认证的作用

客户端每一次请求server的时候,Server通过响应头的形式向Client发送一个身份认证的Cookie,客户端会自动将Cookie保存在Client中

这样一来,当Client每次请求Server时,Client会自动将身份认证的Cookie通过请求头的格式发送给Server,Server即可验明Client身份 image.png

Cookie不具备安全性

Cookie存在Client中,可以被读写

  • 容易被伪造
  • 暴露隐私信息

不要把用户身份信息、密码等重要信息放到Cookie中

Session

Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上,Session的使用依赖于Cookie

如果说Cookie机制是通过检查客户身上的“通行证”来确定客户身份的话,那么Session机制就是通过检查服务器上的“客户明细表”来确认客户身份。Session相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了。

image.png

Session的局限性

由于Session需要Cookie配合才能实现,而Cookie默认不支持跨域访问
因此涉及到前端跨域请求后端接口时,需要做许多额外配置,才能实现

Session与Cookie的区别

  1. Session存储在服务器端,Cookie存储在客户端
  2. Session没有数据大小限制,Cookie有
  3. Session数据安全,Cookie相对于不安全

| Express中使用Session认证

使用express-session中间件来实现Session认证

  1. 安装
npm install express-session
  1. 配置
// 配置session中间件
const session = require('express-session');
app.use(
    session({
        secret: 'dawdawdawdawd', // 任意字符串
        resave: false,
        saveUninitialized: true,
    })
);
  1. 使用
// 1.创建服务器
const express = require('express');
const app = express();

// 解析post请求
const bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({ extended: true }));

// 配置session中间件
const session = require('express-session');
app.use(
    session({
        secret: 'dawdawdawdawd', // 任意字符串
        resave: false,
        saveUninitialized: true,
    })
);

// 2.开启服务器
app.listen(3000, () => {
    console.log('Server running at http://127.0.0.1:3000/');
});

// 3. 监听客户端的GET和POST请求
app.post('/api/login', (req, res) => {
    console.log(req.body, '---');
    if (req.body?.username !== 'admin' || req.body?.password !== 'admin') {
        return res.send({ state: 1, msg: '登录失败' });
    }

    // 向Session中存数据
    req.session.user = req.body;
    req.session.isLogin = true;

    res.send({ state: 0, msg: '登录成功' });
});

// 从Session中取数据
app.get('/api/username', (req, res) => {
    if (!req.session.isLogin) {
        return res.send({ state: 1, msg: '获取信息失败' });
    }
    res.send({ state: 0, msg: '获取信息成功', userinfo: req.session.user });
});


app.get('/api/logout', (req, res) => {
    // 清空Session数据
    req.session.destroy();
    res.send({ state: 0, msg: '退出登录成功' });
});

| Express中使用Token认证(JWT认证)

JWT(JSON Web Token)是目前最流行的跨域认证解决方案

当前端需要跨域请求后端接口时,推荐使用JWT认证机制

工作原理

image.png 用户信息通过Token字符串的形式保存在浏览器中
服务器通过还原Token来认证用户身份

JWT的组成

JWT通常由三部分组成

  • Header(头部)
  • Payload(有效荷载)
  • Signature(签名)

Header和Signature是安全性相关部分,Payload是用户信息

头部.有效荷载.签名

示例

// JSON 对象
{
  "姓名": "三木",
  "角色": "管理员"
}
// 加密后
eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ1c2VybmFtZSIsIm5iZiI6MTYzMjI3NzU1NCwiaXNzIjoiY3J1c2giLCJleHAiOjE2MzIyNzc2NTQsImRlbW8iOiLlj6_lrZjlgqjkv6Hmga8iLCJpYXQiOjE2MzIyNzc1NTQsImRlbW8yIjoi5Y-v5a2Y5YKo5L-h5oGvMiJ9.OuqG5Ha_Ofmh5R9Et1vqLYSAlIO85oW9D9Jq9cKKYODO643ZLiDTyQs8dl3PLsZ-_5t0xv6kfKhCzCkCYznBNA

JWT的使用

客户端收到服务器的JWT之后会存储在localStorage或sessionStorage中
此后每次请求服务器都要携带这个token进行请求,推荐写在HTTP请求头的Authorization字段中

Authorization:Bearer JWT加密后的token

注意:要在token前面加Bearer这是规范
Bearer代表Authorization头定义的schema ,除了Bearer,还有其它的一些 schemas  ,   标准规范请查看文档地址: 规范地址

  1. 安装JWT相关包
npm install jsonwebtoken express-jwt
  • jsonwebtoken 加密用
  • express-jwt 解密用
  1. 导入相关包
// 生成JWT字符串
const jwt = require('jsonwebtoken');
// 解析JWT生成的字符串
const expressJWT = require('express-jwt');
  1. 定义秘钥 为了保证JWT的安全性,防止被破解,定义一个用于加密和解密的秘钥
const secretKey = 'dawdahhjhfk ^d^'; // 字符串,自定义
  1. 登录成功后生成JWT 使用jsonwebtoken提供的sign()方法加密
app.post('/api/login', (req, res) => {
    console.log(req.body, '---');
    if (req.body?.username !== 'admin' || req.body?.password !== 'admin') {
        return res.send({ status: 1, msg: '登录失败' });
    }

    res.send({
        status: 0,
        msg: '登录成功',
        token: jwt.sign(
            {
                username: req.body?.username,
            }, // 加密的json
            secretKey, // 秘钥
            { expiresIn: '60s' } // 过期时间
        ),
    });
});

5.解析JWT为JSON对象

// 挂载解析中间件
// unless方法指定哪些接口不需要访问权限
app.use(
    expressJWT({
        algorithms: ['HS256'], // 要指定加密算法(非对称加密算法HS266)
        secret: secretKey,
    }).unless({ path: [/^\/api\//] })
);

// 当用户访问需要认证的接口时拿到解析好的数据返回
app.get('/user/getinfo', (req, res) => {
    // req.user 里会存放解析好的数据
    console.log(req.user);

    res.send({
        status: 0,
        msg: '获取用户信息成功',
        data: req.user,
    });
});

6.捕获解析JWT失败后产生的错误

app.use((err, req, res, next) => {
    // 解析token错误了
    if (err.name === 'UnauthorizedError') {
        return res.send({
            status: 401,
            meg: '无效token',
        });
    }

    // 其他错误
    res.send({
        status: 500,
        meg: '未知错误',
    });
});

完整代码

/* JSON web token 初体验 */ 

// 1. 导入相关包
const jwt = require('jsonwebtoken'); // 生成JWT字符串
const expressJWT = require('express-jwt'); // 解析JWT生成的字符串

// 2. 设置秘钥
const secretKey = 'dawdahhjhfk';

// 3. 挂载解析中间件
const option = {
        algorithms: ['HS256'], // 要指定加密算法(非对称加密算法HS266)
        secret: secretKey,
};

// unless方法指定哪些接口不需要访问权限
app.use(expressJWT(option).unless({ path: [/^\/api\//] }));

// 4.设置错误中间件捕获解析错误触发
app.use((err, req, res, next) => {
    // 解析token错误了
    if (err.name === 'UnauthorizedError') {
        return res.send({
            status: 401,
            meg: '无效token',
        });
    }

    // 其他错误
    res.send({
        status: 500,
        meg: '未知错误',
    });
});

// 5.写接口
// 登录接口
app.post('/api/login', (req, res) => {
    console.log(req.body, '---');
    if (req.body?.username !== 'admin' || req.body?.password !== 'admin') {
        return res.send({ status: 1, msg: '登录失败' });
    }

    res.send({
        status: 0,
        msg: '登录成功',
        token: jwt.sign(
            {
                username: req.body?.username,
            }, // 加密的json
            secretKey, // 秘钥
            { expiresIn: '60s' } // 过期时间
        ),
    });
});

// 获取用户信息接口
app.get('/user/getinfo', (req, res) => {
    // req.user 里会存放解析好的数据
    console.log(req.user);

    res.send({
        status: 0,
        msg: '获取用户信息成功',
        data: req.user,
    });
});