理解并简易实践JWT身份验证与授权机制
1. 关于JWT:
(1). JWT(json web token)是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准
(2). JWT的声明一般被用于在身份提供者和服务提供者之间传递被认证的用户身份信息,以便于从资源服务器获取资源。最为常见的场景就是用户登录认证
(3). 因为数字签名的存在,这些信息是可信的,JWT可以使用HMAC算法或者是RSA的公私密钥对进行签名
2. 主要的应用场景:
(1). 身份认证在这种场景下,一旦用户完成了登录,在接下来的每个请求中包含JWT, 可以用来验证用户的身份信息以及对路由,服务和资源的访问进行权限的验证
(2). 信息交换在通信双方之间使用JWT对数据进行编码是一种非常安全的方式,由于它的信息经过签名,可以保证发送者发送的信息是没有经过伪造的
3. JWT的结构: JWT包含了使用 . 分隔的三部分内容
(1). Header: 头部, 如{typ: “JWT”, alg:“HS256”}
(2). Payload: 负载 (也就是进行签名的数据, 如{name: “chensir”})
(3). Signature: 签名
3.1 Header:
在header中通常包含了两部分: token类型和采用的加密算法: {“alg”: “HS256”, “typ”: “JWT”}
之后这一部分内容使用Base64Url编码后组成了JWT结构的第一部分
3.2 Payload:
负载就是存放有效信息的地方,这个名字像是指在货车上承载的货物,这些有效信息包含三个部分:
(1). 标注中注册的声明
(2). 公共的声明
(3). 私有的声明
3.2.1 标准中注册的声明:
iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间,是一个秒数
nbf: 定义在什么时间之前,该jwt都是不可用的
iat: jwt的签发时间
3.2.2 公共的声明:
公共的声明可以添加任何信息,一般添加用户的相关信息或其他业务需要的必要信息,但不建议添加敏感信息,因为该部分在用户端可被解密
3.2.3 私有的声明:
私有的声明式提供者和消费者所共同定义的声明,一般不建议存放铭感信息,因为base64是对称解密的,意味着该部分信息可归为明文信息
3.3 Signature
(1). 创建签名需要使用编码后的header 和 payload 以及一个密钥
(2). 使用header中指定的签名算法进行签名, 例如希望使用HMAC HS256算法, 那么签名应该使用下列方式创建:
HMACSHA256(base64UrlEncode(header) + “.” + base64UrlEncode(payload), secret)
(3). 签名用于验证消息的发送者以及消息是否经过篡改
(4). 完整的JWT的完整的输出是以 . 分隔三段base64编码
(5). 密钥secret是保存在服务端的,服务端会根据这个密钥生成token和验证。
4. 在Express中简单使用JWT授权认证实例:
const express = require("express")
const bodyParser = require("body-parser")
const jwt = require("./utils/jwt-simple")
// 这里由于简单实现,所以使用一个数组简单存取一下用户
const USER = [];
// JWT签名的密钥,这个是可以自定义的
const SECRET = "ChenSir#$_!"
let app = express();
app.use(bodyParser.json())
// 定义一个注册的接口, 大家不用深究具体的逻辑
app.post('/reg', function(req, res, next){
let user = req.body;
USER.push(user)
res.json({
code: 0,
msg: "ok"
})
})
// 定义登录接口
app.post("/login", function(req, res, next) {
let user = req.body;
let id = USER.findIndex(item => {
return item.username == user.username && item.password == user.password;
});
if(id != -1){
// 校验成功后使用jwt.encode()进行token获取, 需要传入payload和secret
let token = jwt.encode({
id,
username: user.username
}, SECRET)
res.json({
token
})
}else{
res.json({
code: -1,
msg: "上传信息有误"
})
}
})
// 获取信息接口,添加中间件进行权限校验
app.get("/info", function (req, res, next){
let authorization = req.headers["authorization"];
if(authorization){
let token = authorization.split(" ")[1];
try {
// 对token信息进行解码校验,使用jwt.decode()处理token解析出用户信息
let user = jwt.decode(token, SECRET)
req.user = user;
next(); // 成功取到用户
}catch(e){
res.status(401).send("Not Allowed");
}
}else{
res.status(401).send("Not Allowed");
}
}, function (req, res, next){
res.json(req.user)
})
app.listen(8080, function(){
console.log("server listening on 8080")
})
5. 实现简易的JWT:
let jwt = {
// 根据token和secret取出token中的值
decode(token, secret){
// 取出token中的数字签名进行解析
let [header, content, sign] = token.split(".");
// 解析出header 和 content 的内容
let h = JSON.parse(this.fromBase64ToString(header))
let c = JSON.parse(this.fromBase64ToString(content));
// 校验签名
if(sign === this.sign([header, content].join('.'), secret)){
// TODO: 这里还可以对content中国的一些option进行处理判断, 如exp的过期时间等
return c;
}else{
throw new Error("Not Allowed")
}
},
// 将base64字符串解析成原始的字符串
fromBase64ToString(base64){
return Buffer.from(base64, "base64").toString('utf8');
},
// 将普通字符串编码成base64字符串
toBase64(str){
return Buffer.from(str).toString('base64')
},
// 进行数字签名,这里使用了crypto库中的sha256算法
sign(str, secret){
// 使用crypto进行token的数字签名
return require("crypto").createHmac('sha256', secret).update(str).digest('base64')
},
// 编码获取到token
encode(payload, secret){
let header = this.toBase64(JSON.stringify({'typ': 'JWT', 'alg': "HS256"}))
let content = this.toBase64(JSON.stringify(payload));
// 签名使用: header + . + content
let sign = this.sign([header, content].join('.'), secret)
return [header, content, sign].join('.')
},
}
module.exports = jwt;
- 关于JWT的原理解析就说到这里,大家 加油!!
上一篇: 用JWT实现Token的身份验证:生成、检验与在请求中的应用
下一篇: 登录认证与JWT