| 什么是身份认证
身份认证Authentication又叫鉴权,指通过一定手段完成对用户的身份确认
-
日常身份认证:微信支付、高铁验票
-
web中的手机验证码登录、邮箱密码登录、二维码登录等
由于HTTP协议是无状态性的,客户端的每次HTTP请求都是独立,连续多个请求之间没有直接的关系,服务器并不会保留每次请求状态
因此为了突破HTTP无状态限制,需要身份认证
说到身份认证就不得不提一下Cookie与Session
Cookie
Cookie是存储在用户浏览器中的一段不超过4KB的字符串
由一个名称、值、和有效期、安全性、使用范围的可选属性组成
不同域名下Cookie各自独立,每当客户端发起请求时,会把当前域名下未过期的Cookie一同发送到服务器
特性
- 自动发送
- 域名独立
- 过期时限
- 4KB限制
Cookie在身份认证的作用
客户端每一次请求server的时候,Server通过响应头的形式向Client发送一个身份认证的Cookie,客户端会自动将Cookie保存在Client中
这样一来,当Client每次请求Server时,Client会自动将身份认证的Cookie通过请求头的格式发送给Server,Server即可验明Client身份
Cookie不具备安全性
Cookie存在Client中,可以被读写
- 容易被伪造
- 暴露隐私信息
不要把用户身份信息、密码等重要信息放到Cookie中
Session
Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上,Session的使用依赖于Cookie
如果说Cookie机制是通过检查客户身上的“通行证”来确定客户身份的话,那么Session机制就是通过检查服务器上的“客户明细表”来确认客户身份。Session相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了。
Session的局限性
由于Session需要Cookie配合才能实现,而Cookie默认不支持跨域访问
因此涉及到前端跨域请求后端接口时,需要做许多额外配置,才能实现
Session与Cookie的区别
- Session存储在服务器端,Cookie存储在客户端
- Session没有数据大小限制,Cookie有
- Session数据安全,Cookie相对于不安全
| Express中使用Session认证
使用express-session中间件来实现Session认证
- 安装
npm install express-session
- 配置
// 配置session中间件
const session = require('express-session');
app.use(
session({
secret: 'dawdawdawdawd', // 任意字符串
resave: false,
saveUninitialized: true,
})
);
- 使用
// 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认证机制
工作原理
用户信息通过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 , 标准规范请查看文档地址: 规范地址
- 安装JWT相关包
npm install jsonwebtoken express-jwt
- jsonwebtoken 加密用
- express-jwt 解密用
- 导入相关包
// 生成JWT字符串
const jwt = require('jsonwebtoken');
// 解析JWT生成的字符串
const expressJWT = require('express-jwt');
- 定义秘钥 为了保证JWT的安全性,防止被破解,定义一个用于加密和解密的秘钥
const secretKey = 'dawdahhjhfk ^d^'; // 字符串,自定义
- 登录成功后生成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,
});
});