JWT
JWT 即 JSON Web Token,是一种用于在网络应用间安全传递声明的开放标准(RFC 7519)。它通常由三部分组成,使用 JSON 对象作为声明载体,并通过数字签名确保其安全性,在身份验证和信息交换场景中被广泛应用。以下为你详细介绍:
结构
JWT 通常由三部分组成,各部分之间用点(.)分隔,其格式为 header.payload.signature。
- Header(头部) :包含两部分信息,令牌的类型(通常是 JWT)和使用的签名算法,如 HMAC SHA256 或 RSA。它会被 Base64Url 编码形成 JWT 的第一部分。
{
"alg": "HS256",
"typ": "JWT"
}
-
Payload(负载) :包含声明(Claims),声明是关于实体(通常是用户)和其他数据的声明。声明分为三种类型:
-
注册声明:如
iss(发行人)、sub(主题)、aud(受众)等,这些是预定义的声明,但使用并非强制。 -
公开声明:由各方自由定义。
-
私有声明:在同意使用的各方之间定义的声明。
-
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
- Signature(签名) :为了创建签名部分,需要使用编码后的 Header、编码后的 Payload、一个密钥(
secret)和 Header 中指定的签名算法。例如,使用 HMAC SHA256 算法时,签名将按以下方式创建:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
签名用于验证消息在传输过程中没有被更改,并且在使用私钥签名的情况下,还可以验证 JWT 的发送者的身份。
工作原理
- 用户登录:用户向服务器发送包含用户名和密码的登录请求。
- 服务器验证:服务器验证用户的凭证,如果验证通过,服务器会根据预定义的规则生成一个 JWT。
- 返回 JWT:服务器将生成的 JWT 返回给客户端。
- 客户端存储和使用:客户端收到 JWT 后,通常将其存储在本地(如
localStorage或cookie)。在后续的请求中,客户端会在请求头中添加Authorization字段,其值为Bearer <JWT>,将 JWT 发送给服务器。 - 服务器验证 JWT:服务器接收到请求后,会从请求头中提取 JWT,并使用之前生成 JWT 时的密钥对其进行验证。如果验证通过,服务器会认为请求是合法的,并返回相应的响应。
优点
- 无状态:JWT 是无状态的,服务器不需要在会话中存储用户的状态信息,这使得服务器更容易扩展,并且可以方便地处理跨域请求。
- 可扩展性:由于 JWT 可以包含自定义的声明,因此可以根据需要添加额外的信息,如用户角色、权限等。
- 跨域支持:JWT 可以通过 HTTP 请求头或 URL 参数进行传输,因此可以很方便地在不同的域名之间传递,适用于前后端分离的架构和微服务架构。
缺点
- 安全性风险:如果 JWT 的密钥泄露,攻击者可以伪造合法的 JWT,从而绕过身份验证。此外,由于 JWT 通常会在客户端存储,存在被 XSS(跨站脚本攻击)和 CSRF(跨站请求伪造)攻击的风险。
- 令牌体积:JWT 通常包含较多的信息,因此其体积相对较大,可能会增加网络传输的负担。
- 难以撤销:一旦 JWT 被签发,在其过期之前很难撤销,除非在服务器端维护一个黑名单。
应用场景
- 身份验证:在用户登录后,服务器生成一个 JWT 并返回给客户端,客户端在后续的请求中携带该 JWT,服务器通过验证 JWT 来确认用户的身份。
- 信息交换:JWT 可以安全地在不同的服务之间传递信息,例如在微服务架构中,各个服务可以通过验证 JWT 来获取用户的相关信息。
Express 使用JWT
安装依赖
1. 安装依赖
首先,需要安装 jsonwebtoken 库,它可以帮助我们生成和验证 JWT。在项目根目录下使用以下命令进行安装:
npm install jsonwebtoken
1. 生成 JWT
运用 jwt.sign() 方法能够生成 JWT,此方法接收三个主要参数:
-
payload:要包含在 JWT 里的信息,通常是用户 ID、角色之类的数据。 -
secretOrPrivateKey:用于签名的密钥,它能保证 JWT 的完整性和安全性。 -
options:可选参数,可设置 JWT 的有效期、算法等。
const jwt = require('jsonwebtoken');
// 定义密钥
const secretKey = 'yourSecretKey';
// 定义 payload
const payload = {
userId: 1,
username: 'user123'
};
// 生成 JWT
const token = jwt.sign(payload, secretKey, { expiresIn: '1h' });
console.log('Generated Token:', token);
2. 验证 JWT
使用 jwt.verify() 方法可以验证 JWT 的有效性,该方法接收三个参数:
token:要验证的 JWT。secretOrPublicKey:用于验证签名的密钥,必须和生成时用的密钥一致。callback:验证完成后的回调函数,包含错误信息和解析后的 payload。
const jwt = require('jsonwebtoken');
const secretKey = 'yourSecretKey';
const token = 'yourGeneratedToken';
jwt.verify(token, secretKey, (err, decoded) => {
if (err) {
console.error('Verification failed:', err.message);
} else {
console.log('Verified Payload:', decoded);
}
});
3. 解码 JWT
jwt.decode() 方法能在不验证签名的情况下对 JWT 进行解码,获取其 payload 信息。不过要注意,这种方式不会验证 JWT 的有效性,所以不能用来做身份验证。
const jwt = require('jsonwebtoken');
const token = 'yourGeneratedToken';
const decoded = jwt.decode(token);
console.log('Decoded Payload:', decoded);
2. 生成 JWT
在用户登录成功后,服务器需要生成一个 JWT 并返回给客户端。以下是一个简单的 Express 示例:
const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
const secretKey = 'your_secret_key'; // 用于签名 JWT 的密钥,应妥善保管
// 模拟用户登录接口
app.post('/login', (req, res) => {
// 这里简单假设用户验证通过
const user = { id: 1, username: 'john_doe' };
// 生成 JWT
const token = jwt.sign(user, secretKey, { expiresIn: '1h' });
res.json({ token });
});
const port = 3000;
app.listen(port, () => {
console.log(`服务器运行在端口 ${port}`);
});
在上述代码中,jwt.sign() 方法用于生成 JWT。它接受三个参数:
- 第一个参数是要包含在 JWT 中的数据(通常是用户信息)。
- 第二个参数是用于签名的密钥。
- 第三个参数是可选的配置对象,可以设置 JWT 的过期时间等信息。
3. 验证 JWT
客户端在后续的请求中需要携带 JWT,服务器则需要验证这个 JWT 的有效性。以下是一个验证 JWT 的中间件示例:
const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
const secretKey = 'your_secret_key';
// 验证 JWT 的中间件
const verifyToken = (req, res, next) => {
const token = req.headers['authorization'];
if (!token) {
return res.status(403).send('没有提供令牌');
}
const tokenWithoutBearer = token.replace('Bearer ', '');
jwt.verify(tokenWithoutBearer, secretKey, (err, decoded) => {
if (err) {
return res.status(401).send('无效的令牌');
}
req.user = decoded; // 将解码后的用户信息挂载到请求对象上
next();
});
};
// 模拟受保护的接口
app.get('/protected', verifyToken, (req, res) => {
res.json({ message: '这是受保护的接口', user: req.user });
});
const port = 3000;
app.listen(port, () => {
console.log(`服务器运行在端口 ${port}`);
});
在上述代码中,verifyToken 是一个中间件函数,用于验证请求头中携带的 JWT。如果验证通过,将解码后的用户信息挂载到 req.user 上,并调用 next() 继续处理请求;如果验证失败,返回相应的错误信息。
4. 完整示例
将登录和受保护接口整合到一个完整的 Express 应用中:
const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
const secretKey = 'your_secret_key';
// 验证 JWT 的中间件
const verifyToken = (req, res, next) => {
const token = req.headers['authorization'];
if (!token) {
return res.status(403).send('没有提供令牌');
}
const tokenWithoutBearer = token.replace('Bearer ', '');
jwt.verify(tokenWithoutBearer, secretKey, (err, decoded) => {
if (err) {
return res.status(401).send('无效的令牌');
}
req.user = decoded;
next();
});
};
// 模拟用户登录接口
app.post('/login', (req, res) => {
const user = { id: 1, username: 'john_doe' };
const token = jwt.sign(user, secretKey, { expiresIn: '1h' });
res.json({ token });
});
// 模拟受保护的接口
app.get('/protected', verifyToken, (req, res) => {
res.json({ message: '这是受保护的接口', user: req.user });
});
const port = 3000;
app.listen(port, () => {
console.log(`服务器运行在端口 ${port}`);
});
通过以上步骤,你可以在 Express 应用中实现基本的 JWT 身份验证和授权功能。
自己写一遍完整的+测试
const express = require('express');
const app = express();
const port = 3000;
const yaml = require('js-yaml')
const mysql = require("mysql2/promise");
const fs = require('fs')
const bodyParser = require('body-parser');
const cors = require('cors');
const jwt = require('jsonwebtoken');
// 定义密钥
const secretKey = 'yourSecretKey';
// 生成 JWT
(async()=>{
const db = yaml.load(fs.readFileSync('./db.yaml', 'utf8'))
// 使用 body-parser 中间件解析 JSON 请求体
app.use(bodyParser.json());
// // 解决跨域
app.use(cors());
// 创建连接
const connection = await mysql.createConnection({
host:db.host, // 主机地址
port:db.port, // 端口 默认为 3306
database:db.database, // 数据库名称
user:db.user, // 用户名
password:db.password // 密码
});
// 注册账户接口
app.post('/register', async (req, res) => {
const { username, password } = req.body;
// 验证请求体
if (!username || !password) {
res.status(400)
return res.json({
message: '缺少参数'
});
}
try {
// 检查用户名是否已存在
const [rows, fields] = await connection.execute('SELECT * FROM users WHERE username = ?', [username]);
if (rows.length > 0) {
res.status(409)
return res.json({
message: '用户名已存在'
});
}
// // 插入新用户
await connection.execute('INSERT INTO users (username, password ) VALUES (?, ?)', [username, password]);
res.status(201)
return res.json({ message: '注册成功' });
} catch (error) {
console.error(error);
res.status(500).json({ message: '服务器内部错误' });
}
});
app.post('/login', async (req, res) => {
const { username, password } = req.body;
// 验证请求体
if (!username ||!password) {
res.status(400)
return res.json({
message: '缺少参数'
});
}
try {
// 检查用户名是否已存在
const [rows, fields] = await connection.execute('SELECT * FROM users WHERE username =?', [username]);
if (rows.length === 0) {
res.status(404)
return res.json({
message: '用户名不存在'
});
}
if(rows[0].password !== password){
res.status(401)
return res.json({
message: '密码错误'
})
}
console.log(jwt);
console.log(rows[0].username);
const token = jwt.sign({data:rows[0].username}, secretKey, { expiresIn: '1h' });
// 将 JWT 发送给客户端
res.status(200)
return res.json({
message: '登录成功',
status:200,
token
})
}
catch (error) {
console.error(error);
res.status(500).json({ message: '服务器内部错误' });
}
})
app.post('/test',(req,res)=>{
const token = req.headers.authorization;
console.log(token);
if(!token){
res.status(401)
return res.json({
message: '未授权'
})
}
try {
// 验证 JWT
const decoded = jwt.verify(token, secretKey);
if(decoded){
res.status(200)
return res.json({
message: '授权成功',
status:200,
data:decoded
})
}
res.status(500)
return res.json({
message: '未登录',
status:500,
data:decoded
})
}catch (error) {
console.error(error);
res.status(500).json({ message: '服务器内部错误' });
}
})
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`);
});
})()
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<input type="text" name="" id="name">
<input type="text" name="" id="pwd">
<button id="reg">注册</button>
<button id="login">登录</button>
<button id="token">测试token</button>
<script>
document.querySelector('#reg').onclick = function(){
let name = document.querySelector('#name').value;
let pwd = document.querySelector('#pwd').value;
// 1.创建ajax对象
let xhr = new XMLHttpRequest();
// 2.打开连接
xhr.open('post', 'http://127.0.0.1:3000/register');
xhr.setRequestHeader('Content-Type','application/json');
// 3.发送请求
xhr.send(JSON.stringify({
username:name,
password:pwd
}));
// 4.接收响应
xhr.onreadystatechange = function(){
if(xhr.readyState == 4 && xhr.status == 200){
let res = xhr.responseText;
if(res == 1){
alert('注册成功');
}else{
alert('注册失败');
}
}
}
}
document.querySelector('#login').onclick = function(){
let name = document.querySelector('#name').value;
let pwd = document.querySelector('#pwd').value;
// 1.创建ajax对象
let xhr = new XMLHttpRequest();
// 2.打开连接
xhr.open('post', 'http://127.0.0.1:3000/login');
xhr.setRequestHeader('Content-Type','application/json');
// 3.发送请求
xhr.send(JSON.stringify({
username:name,
password:pwd
}));
// 4.接收响应
xhr.onreadystatechange = function(){
if(xhr.readyState == 4 && xhr.status == 200){
let res = xhr.responseText;
if(xhr.status ==200){
const {token} =JSON.parse(res);
localStorage.setItem('token',token);
}
// if(res == 1){
// alert('注册成功');
// }else{
// alert('注册失败');
// }
}
}
}
document.querySelector('#token').onclick = function(){
// 1.创建ajax对象
let xhr = new XMLHttpRequest();
// 2.打开连接
xhr.open('post', 'http://127.0.0.1:3000/test');
xhr.setRequestHeader('Content-Type','application/json');
let res = localStorage.getItem('token');
if(res){
xhr.setRequestHeader('Authorization',res);
}
// 3.发送请求
xhr.send(JSON.stringify({
}));
// 4.接收响应
xhr.onreadystatechange = function(){
if(xhr.readyState == 4 && xhr.status == 200){
let res = xhr.responseText;
}
}
}
</script>
</body>
</html>