JWT入门介绍了JSON Web Tokens (JWT) 这一紧凑且安全的信息传输标准,适用于身份验证和授权过程。文章详细解释了JWT的工作原理、组成部分及其优势应用场景,并提供了创建和验证JWT的示例代码。JWT的存储方式和安全性也得到了深入探讨,确保了在实际项目中的有效应用。
1. JWT简介
什么是JWT
JSON Web Tokens (JWT) 是一种开放标准 (RFC 7519),用于在各方之间安全地传输信息。它以紧凑的形式携带声明,通常用于身份验证和授权过程。JWT 由三部分组成,每部分之间由点(.
)分隔:头部、载荷和签名。
JWT 典型格式如下:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
JWT的工作原理
JWT 的核心工作流程包括创建、签名、验证和解码。具体步骤如下:
- 创建JWT:客户端向服务器发送请求,服务器根据认证信息(如用户名和密码)生成 JWT。
- 签名JWT:服务器使用密钥(通常是私钥)对 JWT 进行签名,确保 JWT 的完整性和防篡改。
- 传输JWT:JWT 被传输到客户端,客户端可以将其存储在本地(如 localStorage 或 session storage)。
- 验证JWT:当客户端需要访问受保护资源时,它会将 JWT 附加到请求的 Authorization 头中。服务器收到请求后,验证 JWT 的有效性,包括签名和过期时间等。
- 解码JWT:服务器解析 JWT 载荷中的信息,使用这些信息来决定如何处理请求。
JWT的优势与应用场景
JWT 的优势包括:
- 紧凑性:JWT 是一种紧凑的格式,适合通过 URL、POST 请求体、Cookie 等方式传输。
- 无状态:服务器不需要存储 JWT 的信息,减轻了数据库负担。
- 签名:JWT 可以被签名,从而提高了安全性。
- 自带信息:JWT 包含了用户信息,服务器可以通过解析 JWT 来获取必要的用户信息。
JWT 的应用场景包括:
- 用户认证:使用 JWT 进行用户认证,实现无状态的认证方案。
- 跨域共享:JWT 可以轻松地在多个域之间共享用户身份。
- 单点登录:实现单点登录(SSO),用户在一个应用登录后,其他应用也可以通过 JWT 进行认证。
- 资源访问控制:通过解析 JWT 来控制用户对资源的访问权限。
2. JWT的组成部分
JWT 由三部分组成:头部、载荷和签名。每部分都有特定的作用。
头部(Header)
头部包含两个部分,分别是令牌的类型(JWT)和所使用的签名算法(例如 HMAC SHA256 或 RSA)。头部采用 Base64 编码。
示例头部:
{
"alg": "HS256",
"typ": "JWT"
}
载荷(Payload)
载荷是 JWT 的主体部分,包含声明(claims)。声明是关于实体(通常为用户)和其他数据的声明。例如,sub
是用户的身份,iat
是声明生成的时间。
示例载荷:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"iat": 1516239022
}
签名(Signature)
签名用于确保 JWT 的完整性和防篡改。签名是通过使用加密算法(例如 HMAC SHA256 )和头部中指定的密钥来生成的。
示例签名:
HMACSHA256(
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
"secret",
"eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ"
)
生成的 JWT 字符串如下:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
3. 创建JWT
选择算法
选择合适的加密算法对 JWT 的安全性至关重要。常见的算法包括:
- HMAC SHA256:使用密钥生成签名,适用于服务器到服务器的通信。
- RS256:使用 RSA 加密算法生成签名,适用于公钥/私钥场景。
示例代码(使用 HMAC SHA256):
const jwt = require('jsonwebtoken');
const token = jwt.sign(
{
sub: '1234567890',
name: 'John Doe',
admin: true,
iat: Math.floor(Date.now() / 1000)
},
'shhhh',
{
algorithm: 'HS256'
}
);
console.log(token);
生成JWT字符串
使用选择的算法生成 JWT 字符串,包含头部、载荷和签名三个部分。
示例代码(使用 HMAC SHA256):
const jwt = require('jsonwebtoken');
const payload = {
sub: '1234567890',
name: 'John Doe',
admin: true,
iat: Math.floor(Date.now() / 1000)
};
const secret = 'shhhh';
const token = jwt.sign(payload, secret, { algorithm: 'HS256' });
console.log(token);
使用工具快速创建JWT
使用在线工具快速创建 JWT,如 jwt.io。在该网站中,你可以选择算法,输入头部、载荷和密钥,自动生成 JWT 字符串。
4. 验证JWT
验证签名
签名验证确保 JWT 的完整性和防篡改。服务器使用相同的密钥来验证签名。
示例代码:
const jwt = require('jsonwebtoken');
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
const secret = 'shhhh';
try {
const decoded = jwt.verify(token, secret, { algorithms: ['HS256'] });
console.log(decoded);
} catch (err) {
console.log('Invalid token!');
}
验证过期时间
JWT 可以包含过期时间(exp
),服务器需要验证 JWT 是否在有效期内。
示例代码(包含过期时间):
const jwt = require('jsonwebtoken');
const payload = {
sub: '1234567890',
name: 'John Doe',
admin: true,
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + (60 * 60) // 1 hour expiration
};
const token = jwt.sign(payload, 'shhhh', { algorithm: 'HS256' });
console.log(token);
验证令牌的有效性
除了验证签名和过期时间,还需要确保 JWT 在传输过程中没有被篡改或损坏。
示例代码:
const jwt = require('jsonwebtoken');
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
const secret = 'shhhh';
try {
const decoded = jwt.verify(token, secret, { algorithms: ['HS256'] });
console.log(decoded);
} catch (err) {
console.log('Invalid token!');
}
验证过期时间示例(已过期)
const jwt = require('jsonwebtoken');
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
const secret = 'shhhh';
const payload = {
sub: '1234567890',
name: 'John Doe',
admin: true,
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) - 60 // 已过期
};
const token_expired = jwt.sign(payload, secret, { algorithm: 'HS256' });
try {
const decoded = jwt.verify(token_expired, secret, { algorithms: ['HS256'] });
console.log(decoded);
} catch (err) {
console.log('Token has expired!');
}
5. 使用JWT实现用户认证
前端请求JWT
前端可以通过表单提交用户名和密码,服务器验证用户信息后返回 JWT。
示例代码(前端):
fetch('/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: 'john.doe',
password: 'secret'
})
})
.then(response => response.json())
.then(data => {
console.log(data.token);
})
.catch(error => console.error('Error:', error));
后端验证JWT
后端收到登录请求后,验证用户信息,生成 JWT 并返回给前端。
示例代码(后端):
const jwt = require('jsonwebtoken');
const express = require('express');
const app = express();
app.use(express.json());
app.post('/login', (req, res) => {
const { username, password } = req.body;
// 模拟用户验证
if (username === 'john.doe' && password === 'secret') {
const payload = {
sub: '1234567890',
name: 'John Doe',
admin: true,
iat: Math.floor(Date.now() / 1000)
};
const token = jwt.sign(payload, 'shhhh', { algorithm: 'HS256' });
res.json({ token });
} else {
res.status(401).json({ error: 'Invalid credentials' });
}
});
app.listen(3000, () => console.log('Server started on port 3000'));
使用JWT进行身份验证
前端在每次请求受保护资源时,需要将 JWT 附加到请求头中。
示例代码(前端):
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
fetch('/protected', {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(response => response.json())
.then(data => {
console.log(data);
})
.catch(error => console.error('Error:', error));
示例代码(后端):
const jwt = require('jsonwebtoken');
const express = require('express');
const app = express();
app.use(express.json());
app.get('/protected', (req, res) => {
const token = req.header('Authorization').split(' ')[1];
try {
const decoded = jwt.verify(token, 'shhhh', { algorithms: ['HS256'] });
console.log(decoded);
res.json({ message: 'Access granted' });
} catch (err) {
res.status(401).json({ error: 'Invalid token' });
}
});
app.post('/login', (req, res) => {
const { username, password } = req.body;
// 模拟用户验证
if (username === 'john.doe' && password === 'secret') {
const payload = {
sub: '1234567890',
name: 'John Doe',
admin: true,
iat: Math.floor(Date.now() / 1000)
};
const token = jwt.sign(payload, 'shhhh', { algorithm: 'HS256' });
res.json({ token });
} else {
res.status(401).json({ error: 'Invalid credentials' });
}
});
app.listen(3000, () => console.log('Server started on port 3000'));
6. JWT存储与安全性
JWT的存储方式
JWT 可以存储在本地(如 localStorage 或 session storage),也可以存储在 cookie 中。选择合适的存储方式取决于具体需求。
安全存储JWT的方法
- HTTPS:确保所有通信都是通过 HTTPS 进行的,防止中间人攻击。
- Access-Control-Allow-Origin:设置合适的 CORS 头,限制 JWT 的来源。
- HttpOnly:如果存储在 cookie 中,设置 HttpOnly 标志防止 JavaScript 访问。
- Secure:如果存储在 cookie 中,设置 Secure 标志,确保只在 HTTPS 连接中使用。
防止JWT被劫持和重放攻击
- 刷新令牌:实现刷新令牌机制,定期更新 JWT,防止长期有效的 JWT 被劫持。
- 有效期限制:设置合理的过期时间,限制 JWT 的有效期。
- 令牌黑名单:维护一个已使用或失效的 JWT 黑名单,防止重放攻击。
示例代码(刷新令牌):
const jwt = require('jsonwebtoken');
const express = require('express');
const app = express();
app.use(express.json());
let refreshToken = '';
app.post('/login', (req, res) => {
const { username, password } = req.body;
// 模拟用户验证
if (username === 'john.doe' && password === 'secret') {
const payload = {
sub: '1234567890',
name: 'John Doe',
admin: true,
iat: Math.floor(Date.now() / 1000)
};
const accessToken = jwt.sign(payload, 'accessSecret', { algorithm: 'HS256' });
const refreshTokenPayload = {
sub: '1234567890',
name: 'John Doe',
admin: true,
iat: Math.floor(Date.now() / 1000)
};
refreshToken = jwt.sign(refreshTokenPayload, 'refreshSecret', { algorithm: 'HS256' });
res.json({ accessToken, refreshToken });
} else {
res.status(401).json({ error: 'Invalid credentials' });
}
});
app.post('/refreshToken', (req, res) => {
const { refreshToken } = req.body;
try {
jwt.verify(refreshToken, 'refreshSecret', { algorithms: ['HS256'] });
const payload = {
sub: '1234567890',
name: 'John Doe',
admin: true,
iat: Math.floor(Date.now() / 1000)
};
const accessToken = jwt.sign(payload, 'accessSecret', { algorithm: 'HS256' });
res.json({ accessToken });
} catch (err) {
res.status(401).json({ error: 'Invalid token' });
}
});
app.get('/protected', (req, res) => {
const token = req.header('Authorization').split(' ')[1];
try {
const decoded = jwt.verify(token, 'accessSecret', { algorithms: ['HS256'] });
console.log(decoded);
res.json({ message: 'Access granted' });
} catch (err) {
res.status(401).json({ error: 'Invalid token' });
}
});
app.listen(3000, () => console.log('Server started on port 3000'));
通过上述步骤,你可以轻松掌握 JSON Web Tokens 的使用方法,并在实际项目中实现用户认证和授权等功能。希望这篇文章对你有所帮助!
共同学习,写下你的评论
评论加载中...
作者其他优质文章