文章目录
一、JWT理论基础
JSON Web Token(JWT)是一种开放标准(RFC 7519),用于在网络上以紧凑且自包含的方式安全地传输信息。JWT 被设计为在用户和服务之间传递声明信息,以便进行身份验证和授权。该标准定义了一种简洁的、自包含的方法,可以传递使用 JSON 对象进行编码的信息,这些信息可以被验证和信任。
JWT 由三个部分组成:Header、Payload 和 Signature。这三部分通过.
符号分隔,并以Base64编码的形式进行表示。
-
Header(头部):
-
Header 包含了两部分信息,分别是 token 的类型(JWT)和使用的签名算法,例如:
json{ "alg": "HS256", "typ": "JWT" }
-
上述例子表示使用 HMAC SHA-256 算法进行签名。
-
-
Payload(负载):
-
Payload 包含了声明(claims)。声明是关于实体(通常是用户)和其他数据的声明。有三种类型的声明:
- 注册声明(Registered claims): 这些是一组预定义的声明,包括
iss
(签发者)、sub
(主题)、aud
(受众)、exp
(过期时间)、nbf
(不可用之前的时间)和iat
(发布时间)。 - 公共声明(Public claims): 可以根据需要定义的声明。
- 私有声明(Private claims): 用于在同意使用它们的双方之间共享信息。
- 注册声明(Registered claims): 这些是一组预定义的声明,包括
-
例如:
json{ "sub": "1234567890", "name": "John Doe", "admin": true }
-
-
Signature(签名):
-
使用编码的 header、编码的 payload 和一个秘密密钥来创建签名部分。签名用于验证消息在传递过程中是否被篡改。
-
例如:
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
-
JWT 的工作流程通常如下:
- 用户进行身份验证,并且身份验证成功后,服务器会生成一个包含用户信息的 JWT,并将其返回给用户。
- 用户在之后的请求中将 JWT 放入请求的头部(通常是
Authorization
头部)中。 - 服务器在接收到请求时,使用之前共享的密钥来验证 JWT 的签名,以确保信息的完整性。
- 如果签名验证成功,服务器可以使用 JWT 中的信息进行授权判断。
优势和特点:
- 简洁性: JWT 使用 JSON 数据格式,具有良好的可读性和编码/解码的便捷性。
- 自包含: JWT 包含了所有需要的信息,避免了每次请求时都需要查询数据库的开销。
- 可靠性: JWT 的签名机制保证了消息的完整性,防止了信息被篡改的可能性。
- 跨域: 可以在不同的域之间进行信息传递,支持跨域应用场景。
需要注意的是,由于 JWT 使用了 Base64 编码,虽然可以在客户端解码查看,但不能修改,因为修改后签名验证将失败。然而,JWT 中的信息仍然是可见的,因此敏感信息应该被妥善处理。
二、JWT使用实例
2.1 服务端代码
编写一个完整的 JWT 项目涉及多个方面,包括用户认证、生成和验证 JWT、路由保护等。下面是一个简单的示例,使用 Node.js 和 Express 框架实现:
首先,确保你已经安装了 Node.js 和 npm。然后,创建一个新目录,执行以下步骤:
-
初始化项目:
bashnpm init -y
-
安装依赖:
bashnpm install express jsonwebtoken body-parser
-
创建一个
server.js
文件:javascriptconst express = require('express'); const jwt = require('jsonwebtoken'); const bodyParser = require('body-parser'); const app = express(); const PORT = 3000; const SECRET_KEY = 'your_secret_key'; app.use(bodyParser.json()); // 用户数据库(示例) const users = [ { id: 1, username: 'user1', password: 'password1' }, { id: 2, username: 'user2', password: 'password2' }, ]; // 登录路由 app.post('/login', (req, res) => { const { username, password } = req.body; // 实际项目中应该通过数据库查询验证用户信息 const user = users.find((u) => u.username === username && u.password === password); if (user) { // 生成 JWT const token = jwt.sign({ userId: user.id, username: user.username }, SECRET_KEY, { expiresIn: '1h' }); res.json({ token }); } else { res.status(401).json({ error: 'Invalid credentials' }); } }); // 保护路由 app.get('/protected', (req, res) => { const token = req.header('Authorization'); if (!token) { return res.status(401).json({ error: 'Unauthorized' }); } // 验证 JWT jwt.verify(token, SECRET_KEY, (err, decoded) => { if (err) { return res.status(401).json({ error: 'Invalid token' }); } res.json({ message: 'You are authorized!', user: decoded }); }); }); app.listen(PORT, () => { console.log(`Server is running on http://localhost:${PORT}`); });
-
运行应用:
bashnode server.js
现在,你的应用将在 http://localhost:3000
上运行。你可以使用 Postman 或其他工具模拟登录请求,然后将生成的 token 用于访问 /protected
路由。
请注意,以上示例是一个简单的演示,实际项目中需要更复杂的用户认证和安全性措施。在生产环境中,你可能需要使用 HTTPS、存储更安全的密钥、使用数据库进行用户验证等。
2.2 客户端代码
客户端部分通常是一个前端应用,可以使用 HTML、CSS、JavaScript(通常使用框架如 React、Vue.js 或 Angular)来实现。在客户端,主要任务包括用户界面的设计、用户登录、JWT 的处理、以及向服务器发起请求等。
以下是一个简单的 HTML、CSS、JavaScript 示例,使用原生 JavaScript 实现:
- 创建一个
index.html
文件:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JWT Demo - Client</title>
</head>
<body>
<h1>JWT Demo - Client</h1>
<div id="login-form">
<label for="username">Username:</label>
<input type="text" id="username" required>
<br>
<label for="password">Password:</label>
<input type="password" id="password" required>
<br>
<button onclick="login()">Login</button>
</div>
<div id="protected-content" style="display: none;">
<h2>Protected Content</h2>
<p id="message"></p>
</div>
<script>
function login() {
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
fetch('http://localhost:3000/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ username, password }),
})
.then(response => response.json())
.then(data => {
if (data.token) {
// 登录成功,显示保护内容
document.getElementById('login-form').style.display = 'none';
document.getElementById('protected-content').style.display = 'block';
document.getElementById('message').innerText = 'Loading...';
// 使用获取的 token 访问受保护的路由
fetch('http://localhost:3000/protected', {
headers: {
'Authorization': data.token,
},
})
.then(response => response.json())
.then(protectedData => {
document.getElementById('message').innerText = `Welcome, ${protectedData.user.username}!`;
})
.catch(error => console.error('Error fetching protected content:', error));
} else {
alert('Invalid credentials');
}
})
.catch(error => console.error('Login error:', error));
}
</script>
</body>
</html>
这个简单的 HTML 文件包含一个登录表单和一个显示受保护内容的部分。当用户成功登录后,它会获取 JWT,并使用该令牌向服务器发起受保护路由的请求。
请注意,这只是一个基础示例,实际项目中应该考虑更多的安全性和用户体验问题。前端应用通常会使用一些框架,以更好地组织和管理代码。
三、JWT流程
以下是一个使用 Mermaid 语法绘制的简单 JWT 流程图:
- 提供用户名和密码 2. 验证用户信息 3. 发送JWT 4. 包含JWT的请求 5. 验证JWT 6. 返回受保护资源 用户 服务器 生成JWT 服务器 授权访问
这个流程图简要描述了 JWT 的使用过程:
- 用户提供用户名和密码。
- 服务器验证用户信息。
- 如果验证成功,服务器生成 JWT。
- 服务器将包含 JWT 的响应发送给用户。
- 用户在后续请求中包含 JWT。
- 服务器验证 JWT 并授权用户访问受保护资源。
请注意,这只是一个高层次的概述,实际流程可能因应用场景而异,例如,在生成 JWT 时可能涉及更多的步骤,而在验证 JWT 时可能涉及更多的安全性检查。