本文是此文的缩略翻译版, 更详细的内容请参考原文. 本文在原文基础上更正了Bearer的问题, 还有自己的一些更新.
本文讲解下如何在express环境下, 使用passport进行JWT身份验证.
目标:
/login用于登录获取token/secret仅对有合法token的用户可访问
工具:
Postman用于测试发送请求
Node, npm
准备工作
创建项目文件夹. 运行以下命令初始化及安装必要的包.
npm init -y npm install --save express body-parser passport passport-jwt jsonwebtoken
创建如下index.js
// file: index.jsvar express = require("express");var app = express();
app.get("/", function(req, res) {
res.json({message: "Express is up!"});
});
app.listen(3000, function() { console.log("Express running");
});运行node index.js即可启动服务器. 强烈建议安装使用nodemon, 它可以监听文件变化, 自动重启服务器, 启动服务器的命令为nodemon index.js.
index
登录
// file: index.jsvar express = require("express");var bodyParser = require("body-parser");var jwt = require('jsonwebtoken');var passport = require("passport");var passportJWT = require("passport-jwt");var ExtractJwt = passportJWT.ExtractJwt;var JwtStrategy = passportJWT.Strategy;创建测试用的用户数组:
var users = [
{ id: 1, name: 'jonathanmh', password: '%2yx4'
},
{ id: 2, name: 'test', password: 'test'
}
];注意, 实际应用中绝对不要明文保存密码.
使用bcrypt加密
passport.js有策略(strategy)的概念. strategy是一些预定义的方法, 它们会在请求抵达真正的路由之前执行. 如果你定义的strategy认定某个请求非法, 则该路由不会被执行, 而是返回401 Unauthorized.
var jwtOptions = {}
jwtOptions.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
jwtOptions.secretOrKey = 'tasmanianDevil';var strategy = new JwtStrategy(jwtOptions, function(jwt_payload, next) { console.log('payload received', jwt_payload); // usually this would be a database call:
var user = users.find(user => user.id === jwt_payload.id); if (user) {
next(null, user);
} else {
next(null, false);
}
});
passport.use(strategy);接下来添加登录路由:
var app = express();
app.use(passport.initialize());// parse application/x-www-form-urlencoded// for easier testing with Postman or plain HTML formsapp.use(bodyParser.urlencoded({
extended: true}));// parse application/jsonapp.use(bodyParser.json())
app.post("/login", function(req, res) { if(req.body.name && req.body.password){ var name = req.body.name; var password = req.body.password;
} // usually this would be a database call:
var user = users.find(user => user.name === name); if( ! user ){
res.status(401).json({message:"no such user found"});
} if(user.password === req.body.password) { // from now on we'll identify the user by the id and the id is the only personalized value that goes into our token
var payload = {id: user.id}; var token = jwt.sign(payload, jwtOptions.secretOrKey);
res.json({message: "ok", token: token});
} else {
res.status(401).json({message:"passwords did not match"});
}
});我们定义的payload中只有id一个claim.
打开Postman:
method: POST
type: x-www-form-urlencoded
login failed
login success
创建JWT验证的秘密路由
app.get("/secret", passport.authenticate('jwt', { session: false }), function(req, res){
res.json("Success! You can not see this without a token");
});打开Postman:
method: GET
inside Headers: 添加一项, Key为Authorization, 字段为
Bearer {token}. 其中{token}代表前面获得的token字符串.
secret
测试秘密路由
可以创建一个测试用的秘密路由用于打印接收到的JWT token.
app.get("/secretDebug", function(req, res, next){ console.log(req.get('Authorization'));
next();
}, function(req, res){
res.json("debugging");
});[nodemon] restarting due to changes...[nodemon] starting `node index.js`Express runningJWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNDc3MTM0NzM4fQ.Ky3iKYcguIstYPDbMbIbDR5s7e_UF0PI1gal6VX5eyI
更新
如何在自定义的路由中访问payload?
自己定义的strategy中的next(null, user);会将这个user的信息写入req.user
因此, 你可以自定义next的第二个参数, 比如向其中写入一些payload的数据, 然后通过req.user访问那些数据.
注意文中直接返回了user, 也就是将所有user信息, 包含password都返回给了req.user. 自己实现的时候返回必要的信息就行了.
如何在browser访问userId?
login方法返回了{ message: 'ok', token: token }, 其中token包含了userId的信息.
token的结构是header.payload.signature.
Token中的header和payload是base64url编码的, 它本身是用HMACSHA256算法进行签名的.
所以对payload进行base64解压缩即可, 浏览器有相应的atob解压, btoa压缩, 详见Base64 encoding and decoding.
function getToken() { let token = localStorage.getJson('token'); if (!token) { return undefined;
} let parts = token.split('.'); if (parts.length !== 3) { return undefined;
} let payload = parts[1]; return JSON.parse(base64url.decode(payload));
}更新
注意! 踩了个坑!! header和payload是base64url编码的(详见rfc7519), 不是base64! 它们之间有一些细微的差别, 比如base64中的+和/在base64url中需要被转换为-和_!
总之, 有一个包叫做base64url, 用这个库解压payload就对了! 别用base64!
我还整理了一个base64编解码的文章, 结果搞了半天不是用base64...蛋疼.
参考
作者:柳正来
链接:https://www.jianshu.com/p/dc9a3302b92a
共同學習,寫下你的評論
評論加載中...
作者其他優質文章



