Build an Instagram clone with AngularJS, Satellizer, Node.js and MongoDB
把下面的方法加到 app.use() 中间件后面去。其实这个方法放在 server.js 哪里都无所谓,知识我们要确保我们在这篇教程里面能比较容易找到。这是一个单纯的 JavaScript 方法,把用户对象当成参数,然后返回一个 token 字符串。
<!-- lang: js -->
function createToken(user) {
var payload = {
exp: moment().add(14, 'days').unix(),
iat: moment().unix(),
sub: user._id
};
return jwt.encode(payload, config.tokenSecret);
}
我用 Moment.js 来生成当前时间和两周之后的日期。你可以用 JavaScript 的内置 Date 对象,但是肯定没有我上面的简洁漂亮。
我们用的 JWT 库(jwt-simple) 中要用到的是这两个方法: encode() 和 decode()。用于 encoded 和 decoded 的对象称之为 JWT Claims,很多地方也把它叫做 payload。你可以在这里查阅到完整的 JSON Web Token 规范草案。上面的这些 claims 都不是必须的 – exp (过期时间), iat (发行日), sub (摘要) 。作为开发者,这都由你控制,根据你的用例来决定 JWT claims 应该包含什么内容。就像我之前提到过的,你甚至可以包含整个用户对象作为 JWT Claims 的一部分,不过通常都是 email 或者 user_id 。
最后 第 5 行的 sub claim 被用来保存用户的唯一标识符 (MongoDB ObjectId),用于标识特定用户。你要问为什么我会用 sub 来代替 id 或者 user_id?我乐意啊,不过也是因为看到 Google 在 OAuth 2.0 OpenID Connect 中也跟我一样用它来作为用户唯一标识符:
接下来的功能是负责阻止未经授权的用户访问受限的路由.举例来说,除非用户得到授权,否则我们是不会希望他们访问 /api/feed 路由(Instagram feed)的。
<!-- lang: js -->
function isAuthenticated(req, res, next) {
if (!(req.headers &amp;&amp; req.headers.authorization)) {
return res.status(400).send({ message: 'You did not provide a JSON Web Token in the Authorization header.' });
}
var header = req.headers.authorization.split(' ');
var token = header[1];
var payload = jwt.decode(token, config.tokenSecret);
var now = moment().unix();
if (now &gt; payload.exp) {
return res.status(401).send({ message: 'Token has expired.' });
}
User.findById(payload.sub, function(err, user) {
if (!user) {
return res.status(400).send({ message: 'User no longer exists.' });
}
req.user = user;
next();
})
}
在 2-4 行 我们首先检查了 HTTP 请求是否有 Authorization 头。为啥?因为 Satellizer 会拦截所有发出的请求,自动的把 JSON Web Token 加到请求头中。如果你想看看 Satellizer 源码的话,下面是负责这部分内容的代码:
<!-- lang: js -->
$httpProvider.interceptors.push(['$q', function($q) {
var tokenName = config.tokenPrefix ? config.tokenPrefix + '_' + config.tokenName : config.tokenName;
return {
request: function(httpConfig) {
var token = localStorage.getItem(tokenName);
if (token) {
token = config.authHeader === 'Authorization' ? 'Bearer ' + token : token;
httpConfig.headers[config.authHeader] = token;
}
return httpConfig;
},
responseError: function(response) {
return $q.reject(response);
}
};
}]);
当然给你看你也不一定能看懂,我只是想说,为什么我们要在 Express 中检查在请求头里面是否有授权内容。提一句,你可以用 Google Chrome 的开发者工具下面的 Network 来查看:
好了,回到 isAuthenticated() 中间件方法。检查完 Authorization 头是否存在之后,我们需要用生成令牌时候的加密字符串来解码令牌。解码之后令牌会变回原来的 claims 对象,比如:
<!-- lang: js -->
{ exp: 1416796466,
iat: 1415586866,
sub: '546024329ac4d00000db0e09' }
注意: 在做所有的的操作之前都检查 JSON Web Token 的确是个好主意。这个中间件假定我们会得到以下格式的令牌: Authorization: Bearer [token]。查看auth0/node-jsonwebtoken source code (从 34 到 84 行)获取更多信息。
首先,检查有效期。我有设置 14 天的令牌有效期,不过,大家的用例都不一样。如果你要做一个设计大量敏感信息的应用,你可以设置过期时间为 1 小时或者更少。
如果令牌没有过期,我们就可以继续检索数据库的文档。在 第 20 行,我们临时 “保存” 用户对象到请求对象中。这样我们可以在 protected 路由里面,通过调用 req.user 来获取用户的信息。比如说:
<!-- lang: js -->
app.get('/protected', isAuthenticated, function(req, res) {
// Prints currently signed-in user object
console.log(req.user);
});
最后我们通过执行 next() 来结束中间件,这个标签告诉 Express 继续执行路由处理。