一.前沿
jwt是json web token缩写。它将用户信息加密到token里,服务器不保存任何用户信息,服务器通过保存的密钥验证token的正确性,只要正确即通过验证。
优点是在分布式系统中,很好地解决了单点登录问题,很容易解决了session共享的问题。比如一个公司有很多相关联的网站,比如A,B,C。通常希望用户在登陆A网站以后,再访问B,C网站能够自动登录(也就是所谓的单点登陆)。
缺点是无法作废已颁布的令牌/不易应对数据过期。比如说,我登陆了A网站,再登录B网站,在token过期之前,token始终是有效的,无法废除token的有效性。
二.jwt原理
JWT的原理是服务器认证以后,生成一个下面所示的对象,发送给用户。
{
name:hello,
roles: 管理员,
expires:2018年11月
}
以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。当然,出于安全的考虑,服务器不能发送明文的数据给用户,通常需要对这个对象进行加密。
三.jwt加密生成token
这里使用了jsonwebtoken来生成token
jwt.sign(payload, secretOrPrivateKey, [options, callback])
1.Payload 部分是一个 JSON 对象,用来存放实际需要传递的数据.比如:
{
id:user.id,
name:user.name
}
2.secretOrPrivateKey:是加密的名字,可以自己定义
3.第三个参数是可选的,主要包括加密方式,过期时间等,一般只需要设置过期时间expiresIn,加密方式默认使用HS256
const rule = {id:user.id,name:user.name};
jwt.sign(rule,keys.secretOrKey,{expiresIn:3600},(err,token) => {
if(err) throw err;
res.json({
success:true,
token:"Bearer " + token
});
})
token组成
最终生成的token如下图所示:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJuYW1lIjoiaGVsbG8iLCJpYXQiOjE1NDMzOTUyMDgsImV4cCI6MTU0MzM5ODgwOH0.
zFiGRNTST8RSY3e1JqWx0SA0S4BCOgNCVsUC4u9JHbY
可以观察到生成的token被 .划分开来,由三部分组成。这三部分分别是Header,
Payload,Signature。其中:
Header是一个对象,描述 JWT 的元数据,通常是下面的样子。
{
"alg": "HS256",
"typ": "JWT"
}
上面代码中,alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT。最后将上面的 JSON 对象使用 Base64URL 算法转成字符串,也就是token的第一部分。
Payload也是一个对象,就是我们上面提到的需要传递的数据组成的对象
Signature 部分是对前两部分的签名,防止数据篡改。
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
四.使用passport验证token(passport-jwt验证jwt类型的token)
passport通常是用来实现登陆验证。这里我们使用passport-jwt来进行验证jwt类型的token。
1.使用passport
app.js
const passport = require('passport');
app.use(passport.initialize());
const JwtStrategy = require('passport-jwt').Strategy,
ExtractJwt = require('passport-jwt').ExtractJwt;
const opts = {}
//得到token
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
//设置token时使用的加密名字
opts.secretOrKey = 'secret';
passport.use(new JwtStrategy(opts, function(jwt_payload, done) {
console.log(jwt_payload);
}));
1. JwtStrategy用来定义策列,是在路由执行之前执行。
2. ExtractJwt里面包含很多函数,这里需要注意的是fromAuthHeaderAsBearerToken**是获取到请求时以Bearer +token得到的token。
注意:他只能获取到以Bearer+一个空格开头的token。因此我们这里的token必须以Bearer+ 一个空格开头。如下所示:
res.json(
{
success:true,
token:"Bearer "+token
})
3.opts对象里面定义的各个参数是我们之前创建token设置的参数
opts.jwtFromRequest:用于从request中获取token
opts.secretOrKey:是之前设置的加密名字
4.passport.use(new JwtStrategy(opts, function(jwt_payload, done) {}))使用passport.use()定义策略。
回调函数中的jwt_payload时一个包含id,name等的对象。
{ name: 'hello', id: 'hello', iat: 1543401227, exp: 1543404827 }
根据payload中的id,我们可以查询是否有这个对象。
回调函数的第二个参数done是一个函数(相当于next),这个函数第一个参数是err,第二个参数是user。我们可以通过done将user进行返回,这样的话在请求中就把user对象添加进去了。如下所示:
passport.use(new JwtStrategy(opts, function(jwt_payload, done) {
// console.log(jwt_payload);
User.findById(jwt_payload.id)
.then((user) => {
if(user){
return done(null,user)
}else{
return done(null,false);
}
})
}));
user.js
这样的话,在其他任何进行路由的地方都可以使用passport.authenticate进行验证.
router.get('/current',passport.authenticate('jwt',{ session: false }),(req,res) => {
console.log(req.user); //{ name: 'hello', id: 'hello', iat: 1543401227, exp: 1543404827 }
});
注意:非常重要的一点就是我们如果使用了passport进行了验证。那么就可以从请求中获取到user,当然前提是调用了done函数,返回了user。