jwt 原理
- 客户端用户使用用户名和密码通过 POST 请求登录或注册
- 服务端确认用户合法,生成一个 JWT
- 将 JWT 返回给客户端,客户端将其保存在本地(一般保存在 localStorage 中)
- 之后客户端向服务端发送 HTTP 请求需要将 JWT 加入请求头中
- 服务器解析 JWT,检查是否合法且有效
- 根据检查结果对客户端做出响应
JWT 结构
服务端生成的 JWT 是一段很长的字符串,由三部分组成,分别为 Header(头部)、Payload(负载)和 Signature(签名),中间用.分隔:
1. Header
头部 Header 是经过 Base64Url[1] 编码的 JSON 对象:
{
“alg”: “HS256”,
“typ”: “JWT”
}
其中,alg是 JWT 所使用的签名算法,默认为HS256,typ即 Token 的类型。
2. Payload
负载 Payload 是一个包含传递数据的 JSON 对象,同样经过了 Base64Url 编码,包含以下可选属性:
iss:发行人
sub:主题
aud:受众群体
exp:到期时间
nbf:生效时间
iat:签发时间
jti:JWT ID
详情请参考 Registered Claim Names。
Payload 中的属性也可以自行添加,但由于 JWT 的内容对任何人都可见,除非对 JWT 进行二次加密,否则不能将任何机密的数据写入其中。
3. Signature
Signature 签名操作是在获取到了 Header 和 Payload 后进行的,比如 Header 中指定的是 HS256 算法,那么会通过以下方式创建 Signature:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
其中的secret为保存在服务端的密钥,一般需要定期更新。完成签名后就将这三部分拼接在一起返回给客户端:
JWT = `${Header}.${Payload}.${Signature}`;
NodeJS 中实现 JWT
JWT 在各种语言中都有实现,如 java-jwt、angular2-jwt、go-jwt-middleware 等,更多的实现可在 auth0-docs 中查看,此处以 NodeJS 中的实现 jsonwebtoken 为例进行用户的认证。
先使用 express-generator 创建项目,安装需要的依赖:
npm install mongoose jsonwebtoken --save
连接 MongoDB 后添加 users 集合的数据库模型:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const userSchema = new Schema({
"username": { type: String, required: true },
"password": { type: String, required: true },
});
module.exports = mongoose.model("User",userSchema, "users");
生成 JWT
为了更好地复用,完成一个公共的生成 JWT 方法:
const jwt = require('jsonwebtoken');
const secret = "zander";
function createJWT(username, sub, exp, strTimer) {
const jwt = jwt.sign({
user: username, // 用户名
sub: sub // 主题
}, secret, {
expiresIn: `${exp}${strTimer}` // 过期时间
})
return jwt;
}
注册接口
router.post('/register', async (req, res) => {
const { username, password } = req.body;
const userData = { username, password };
const saveData = await new User(userData).save();
const jwt = createJWT(username, 'register', 1, 'h');
res.json({
status: 200,
data: saveData,
token: jwt
})
})
postman测试
登陆接口
router.post('/login', async (req, res) => {
const { username, password } = req.body;
const userData = await User.findOne({ username, password });
if (!userData) {
return res.json({
status: 501,
msg: '登录信息有误'
})
}
const jwt = createJWT(username, 'login', 5, 'd');
res.json({
status: 200,
data: userData,
token: jwt
})
})
验证 JWT
服务端生成了 JWT 后不保存,直接发送给客户端,而客户端拿到了 JWT 后将其保存下来,在发送其它请求时需要将 JWT 加入到请求头中:
Authorization: Bearer <token>
然后服务端验证 JWT 是否合法且有效:
// 验证token方法
function verifyJWT(token) {
let decoded;
try {
decoded = jwt.verify(token.split(" ")[1], secret);
} catch (err) {
return err.message;
}
return decoded;
}
// 根据用户名获取密码接口
router.get('/password', async (req, res) => {
const token = req.headers['authorization'];
const { username } = req.query;
if (!token) {
return res.json({
status: 501,
msg: 'Not authorized'
});
}
const info = verifyJWT(token);
if(typeof info === "string"){
return res.json({
status: 501,
msg: info
});
}
const userData = await User.findOne({ username });
res.json({
status: 200,
data: userData.password
})
})
非法请求
正确的请求