跳到主要内容

JavaScript JWT令牌


title: JavaScript JWT令牌 description: 了解JWT令牌的基础知识、工作原理以及在JavaScript中的实际应用,包括完整的代码示例和最佳实践。

什么是JWT令牌?

JWT(JSON Web Token)是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间安全地传输信息作为JSON对象。由于其数字签名的特性,这些信息可以被验证和信任。JWTs可以使用密钥(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。

备注

JWT通常被用于身份验证和信息交换的场景中,尤其适合于构建无状态的认证机制。

JWT的结构

一个JWT令牌由三部分组成,使用点(.)分隔:

xxxxx.yyyyy.zzzzz

这三部分分别是:

  1. Header(头部):通常由两部分组成 - token类型(即JWT)和所使用的签名算法(如HMAC SHA256或RSA)。
  2. Payload(有效载荷):包含声明(claims)。声明是关于实体(通常是用户)和其他数据的陈述。
  3. Signature(签名):用于验证消息在传输过程中没有被更改,对于使用私钥签名的令牌,它还可以验证JWT的发送方是谁。

在JavaScript中使用JWT

安装必要的包

在Node.js项目中,我们通常使用jsonwebtoken库来处理JWT:

bash
npm install jsonwebtoken

创建JWT令牌

下面是在Node.js环境中创建JWT令牌的基本示例:

javascript
const jwt = require('jsonwebtoken');

// 用户数据(将被放入token的payload中)
const userData = {
id: 123,
username: 'example_user',
role: 'user'
};

// 密钥(在实际应用中应该存储在环境变量中)
const SECRET_KEY = 'your_secret_key';

// 创建JWT令牌(默认使用HMAC SHA256算法)
const token = jwt.sign(
userData,
SECRET_KEY,
{ expiresIn: '1h' } // 设置过期时间
);

console.log(token);
// 输出类似:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTIzLCJ1c2VybmFtZSI6ImV4YW1wbGVfdXNlciIsInJvbGUiOiJ1c2VyIiwiaWF0IjoxNjM0NTY3ODkwLCJleHAiOjE2MzQ1NzE0OTB9.qwertyuiopasdfghjklzxcvbnm

验证JWT令牌

验证JWT令牌是确保令牌有效且未被篡改的关键步骤:

javascript
const jwt = require('jsonwebtoken');

// 假设这是从客户端接收到的令牌
const receivedToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';

// 验证令牌
try {
const decoded = jwt.verify(receivedToken, 'your_secret_key');
console.log('令牌验证成功:', decoded);
// 输出:
// 令牌验证成功: { id: 123, username: 'example_user', role: 'user', iat: 1634567890, exp: 1634571490 }
} catch (error) {
console.error('令牌验证失败:', error.message);
}

JWT的工作流程

以下是JWT在典型的Web应用中的工作流程:

实际应用案例

案例1:用户身份验证系统

让我们构建一个简单的用户登录和验证系统:

javascript
const express = require('express');
const jwt = require('jsonwebtoken');
const bodyParser = require('body-parser');

const app = express();
app.use(bodyParser.json());

// 模拟用户数据库
const users = [
{ id: 1, username: 'user1', password: 'password1', role: 'admin' },
{ id: 2, username: 'user2', password: 'password2', role: 'user' }
];

const SECRET_KEY = 'your_super_secret_key';

// 登录路由
app.post('/login', (req, res) => {
const { username, password } = req.body;

// 查找用户
const user = users.find(u => u.username === username && u.password === password);

if (!user) {
return res.status(401).json({ message: '用户名或密码不正确' });
}

// 创建JWT令牌,不包含敏感信息如密码
const token = jwt.sign(
{ id: user.id, username: user.username, role: user.role },
SECRET_KEY,
{ expiresIn: '1h' }
);

res.json({ message: '登录成功', token });
});

// 验证中间件
const authenticateToken = (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN

if (!token) {
return res.status(401).json({ message: '未提供认证令牌' });
}

jwt.verify(token, SECRET_KEY, (err, user) => {
if (err) {
return res.status(403).json({ message: '令牌无效或已过期' });
}

req.user = user;
next();
});
};

// 受保护的路由
app.get('/profile', authenticateToken, (req, res) => {
res.json({
message: '访问成功',
user: req.user
});
});

// 管理员路由
app.get('/admin', authenticateToken, (req, res) => {
if (req.user.role !== 'admin') {
return res.status(403).json({ message: '需要管理员权限' });
}

res.json({ message: '管理员面板', adminData: '敏感数据' });
});

app.listen(3000, () => {
console.log('服务器运行在 http://localhost:3000');
});

案例2:前端存储和使用JWT

在前端React应用中使用JWT的简单示例:

javascript
// 登录组件
function Login() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');

const handleSubmit = async (e) => {
e.preventDefault();

try {
const response = await fetch('http://localhost:3000/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ username, password }),
});

const data = await response.json();

if (response.ok) {
// 存储令牌
localStorage.setItem('token', data.token);
// 重定向到首页或个人资料页
window.location.href = '/profile';
} else {
alert(data.message);
}
} catch (error) {
console.error('登录失败:', error);
}
};

return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="用户名"
required
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="密码"
required
/>
<button type="submit">登录</button>
</form>
);
}

// 个人资料组件
function Profile() {
const [profileData, setProfileData] = useState(null);
const [error, setError] = useState('');

useEffect(() => {
const fetchProfile = async () => {
const token = localStorage.getItem('token');

if (!token) {
setError('请先登录');
return;
}

try {
const response = await fetch('http://localhost:3000/profile', {
headers: {
'Authorization': `Bearer ${token}`,
},
});

const data = await response.json();

if (response.ok) {
setProfileData(data);
} else {
setError(data.message);
// 如果令牌过期或无效,清除本地存储并重定向到登录页面
if (response.status === 401 || response.status === 403) {
localStorage.removeItem('token');
window.location.href = '/login';
}
}
} catch (error) {
setError('获取数据时出错');
console.error(error);
}
};

fetchProfile();
}, []);

if (error) {
return <div className="error">{error}</div>;
}

if (!profileData) {
return <div>加载中...</div>;
}

return (
<div>
<h1>个人资料</h1>
<p>用户名: {profileData.user.username}</p>
<p>角色: {profileData.user.role}</p>

<button onClick={() => {
localStorage.removeItem('token');
window.location.href = '/login';
}}>
登出
</button>
</div>
);
}

JWT安全最佳实践

警告

JWT令牌虽然方便,但也有其安全隐患。以下是一些最佳实践:

  1. 适当设置过期时间:JWT令牌应该有较短的有效期,通常几分钟到几小时。
  2. 使用HTTPS:确保所有包含JWT的通信都通过HTTPS进行。
  3. 不要在JWT中存储敏感数据:JWT的payload虽然是编码的,但不是加密的,任何人都可以解码查看。
  4. 使用安全的签名算法:首选RS256(RSA SHA-256)而不是HS256(HMAC SHA-256)算法,特别是在微服务架构中。
  5. 实现令牌刷新机制:使用刷新令牌(Refresh Token)来获取新的访问令牌,而不是延长访问令牌的有效期。
  6. 安全存储密钥:不要将签名密钥硬编码在代码中,应该使用环境变量。

解决常见问题

JWT令牌过大

JWT令牌可能会变得很大,特别是当包含大量的自定义声明时:

javascript
// 减小JWT大小的技巧
const minimalUserData = {
sub: '123', // subject (用户ID)
role: 'u' // 用简短代码代替完整角色名
};

// 而不是
const verboseUserData = {
userId: 123,
username: 'example_user',
email: 'user@example.com',
fullName: 'Example User',
role: 'user',
permissions: ['read', 'write', 'delete']
};

处理令牌过期

在客户端优雅地处理令牌过期:

javascript
// 使用拦截器统一处理令牌过期(以Axios为例)
import axios from 'axios';

const api = axios.create({
baseURL: 'http://localhost:3000',
});

// 请求拦截器添加令牌
api.interceptors.request.use(config => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});

// 响应拦截器处理令牌过期
api.interceptors.response.use(
response => response,
error => {
if (error.response && (error.response.status === 401 || error.response.status === 403)) {
// 令牌过期或无效
localStorage.removeItem('token');
window.location.href = '/login';
return Promise.reject(new Error('登录已过期,请重新登录'));
}
return Promise.reject(error);
}
);

export default api;

总结

JWT令牌是现代Web应用中实现无状态身份验证的强大工具。它们提供了一种安全地在客户端和服务器之间传输信息的方式,同时保持数据的完整性和真实性。

在本教程中,我们学习了:

  • JWT的基本结构和组成部分
  • 如何在Node.js中创建和验证JWT
  • JWT的典型工作流程
  • 实际应用案例,包括后端API和前端集成
  • JWT安全最佳实践
  • 解决常见JWT问题的策略

通过正确实施JWT认证,你可以构建更安全、可扩展的Web应用,同时简化用户身份验证流程。

进一步学习资源

  1. JWT官方网站 - 包含JWT调试器和库列表
  2. RFC 7519 - JWT规范
  3. Node.js jsonwebtoken库文档

练习

  1. 创建一个简单的Express API,实现JWT认证,包括登录、注册和受保护的路由。
  2. 实现一个令牌刷新机制,使用刷新令牌获取新的访问令牌。
  3. 使用React或Vue创建一个前端应用,与你的JWT认证API集成,实现完整的登录、注册和个人资料功能。

尝试这些练习将帮助你巩固对JWT令牌的理解,并获得实际应用经验!