写给前端的JSON Web Tokens(JWT)
什么是JWT
JWT(JSON Web Tokens)是一种基于JSON的开放标准,用于在网络应用环境间安全地传输信息。核心特点是自包含,所有必要信息都编码在JWT内部。特别适合用于身份验证和授权场景。
为什么使用JWT
- 安全性:JWT的签名保证了令牌的完整性和真实性,确保信息不会在传输过程中被篡改或伪造。同时,由于JWT是基于标准的,可以使用加密算法来保护敏感信息,确保令牌只能被可信的接收方解密。
- 扩展性:由于JWT允许在Payload中添加自定义的声明,因此可以在令牌中携带更多的用户信息和相关权限,满足不同应用的需求。
- 跨平台和语言:JWT是基于JSON的开放标准,可以在不同的编程语言和平台间传递。
- 状态无关性:传统的会话认证在服务端需要保存用户的会话状态,而JWT是无状态的,所有信息都被包含在令牌本身中,这使得服务端不需要保存任何状态信息,从而降低了服务端的负担。
JWT组成结构
JWT由三部分组成,通过.
分割
- 头部(Header):
- 包含关于JWT的元数据,如其类型(通常是
JWT
)和所使用的签名算法(如HS256
、RS256
等)。 - 这部分信息会被Base64Url编码形成JWT的第一个部分。
- 包含关于JWT的元数据,如其类型(通常是
- 载荷(Payload)
- 载荷包含了JWT所携带的实际信息,如用户ID、用户角色、过期时间等。
- 载荷可以分为三类:预定义(Registered)声明、公共(Public)声明和私有(Private)声明。
- 这部分也会被Base64Url编码,形成JWT的第二个部分。
- 签名(Signature):签名是通过将Header和Payload两部分拼接后,使用Header中指定的算法和一个密钥(secret)或公钥/私钥对计算得出的。用于验证JWT的完整性和确保其未被篡改。
示例如下
使用JWT的登录流程
如下图所示,在客户端登录时,服务端会根据必要信息生成并返回JWT,客户端收到后将其存储在cookie
或localStorage
中,在后续的请求中携带传给服务端。服务端对JWT进行验证,验证通过后对请求作出响应。
使用jsonwebtoken登录【可参考】
在Node.js环境下,使用三方库node-jsonwebtoken实现登录功能和验证jwt。
使用jwt.sign()
生成token,使用jwt.verify()
验证token正确性
// 登录
exports.login = catchAsync(async (req, res, next) => {
const { email, password } = req.body;
// 1) 验证是否发送邮箱、密码;
if (!email || !password) {
return next(new AppError('Please provide email and password!', 400));
}
// 2) 根据邮箱密码查询用户是否存在,密码是否正确;
const user = await User.findOne({ email }).select('+password');
if (!user) {
return next(new AppError('There in no user with this email address', 404));
}
const correct = await user.correctPassword(password, user.password);
if (!user || !correct) {
return next(new AppError('Incorrect email or password!'), 400);
}
// 3) 如果正确生成并返回token
createSendToken(user, 200, res);
});
// 创建并发送token
const createSendToken = (user, statusCode, res) => {
// 1)注册令牌
const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRES_IN,
});
// 2)向客户端发送要存储的cookie信息
const cookieOptions = {
expires: new Date(
Date.now() + process.env.JWT_COOKIE_EXPIRES_IN * 24 * 60 * 60 * 1000,
),
httpOnly: true,
};
if (process.env.NODE_ENV === 'production') cookieOptions.secure = true;
res.cookie('jwt', token, cookieOptions);
// 3)返回用户信息
user.password = undefined;
res.status(statusCode).json({
data: 'success',
token,
data: { user },
});
};
// 路由保护
exports.protect = catchAsync(async (req, res, next) => {
// 1) Getting token and check of it's there
let token;
if (
req.headers.authorization &&
req.headers.authorization.startsWith('Bearer')
) {
token = req.headers.authorization.split(' ')[1];
}
if (!token) {
return next(
new AppError('You are not logged in! Please log in to get access', 401),
);
}
// 2) Verification token
const decoded = await promisify(jwt.verify)(token, process.env.JWT_SECRET);
// console.log(decoded);
// 3) Check if user still exists(利用jwt的自包含特性)
const freshUser = await User.findById(decoded.id);
if (!freshUser) {
return next(
new AppError(
'The user belonging to this token does no longer exist.',
401,
),
);
}
// 4) Check if user changed password after the token was issued
if (freshUser.changedPasswordAfter(decoded.iat)) {
return next(
new AppError('User recently changed password! please log in again', 401),
);
}
// GRANT ACCESS TO PROTECT ROUTE
req.user = freshUser;
next();
});
参考
转载自:https://juejin.cn/post/7383205958828933157