在Node.js中使用JSON Web令牌

前端框架和库(例如Ember,Angular和Backbone)是朝着更丰富,更复杂的Web应用程序客户端发展的趋势的一部分。 因此,服务器端组件无需承担许多传统职责,实际上,它们变得更像API。 这种API方法可以使应用程序的传统“前端”和“后端”部分更好地分离。 一组开发人员可以独立于前端工程师而构建后端,其另外的好处是测试变得更加简单。 例如,这种方法还使构建与Web应用程序共享相同后端的移动应用程序变得更加容易。

提供API时面临的挑战之一是身份验证。 在传统的Web应用程序中,服务器通过执行以下两项操作来响应成功的身份验证请求。 首先,它使用某种存储机制创建一个会话。 每个会话都有其自己的标识符(通常是一个长的半随机字符串),该标识符用于在将来的请求中检索有关该会话的信息。 其次,该信息通过指示其设置cookie的标头发送给客户端。 浏览器自动将会话ID cookie附加到所有后续请求,从而允许服务器通过从存储中检索适当的会话来识别用户。 这就是传统的Web应用程序如何绕过HTTP无状态这一事实。

API应该设计为真正的无状态。 这意味着没有登录或注销方法,也没有会话。 API设计人员也不能依赖Cookie,因为不能保证将通过Web浏览器发出请求。 显然,我们需要一种替代机制。 本文研究了旨在解决该问题的一种可能的机制-JSON Web令牌或JWT(发音为jot)。 本文中的示例在后端使用Node's Express框架,在客户端使用Backbone。

背景

让我们简要地介绍一些保护API的常用方法。

一种是使用HTTP基本身份验证。 在正式的HTTP规范中定义,这实际上涉及在服务器响应上设置标头,以指示需要认证。 客户端必须通过将其凭据(包括密码)附加到每个后续请求中来进行响应。 如果凭据匹配,则将用户信息作为变量提供给服务器应用程序。

第二种方法非常相似,但是使用应用程序自己的身份验证机制。 这通常涉及对照存储的凭证检查提供的凭证。 与HTTP基本身份验证一样,这要求每次调用都提供用户凭据。

第三种方法是OAuth (或OAuth2)。 在很大程度上设计为针对第三方服务进行身份验证,至少在服务器端实施,这可能是相当具有挑战性的。

第四种方法是使用令牌。 这就是我们在本文中要探讨的内容。 我们将研究在前端和后端都利用JavaScript的实现。

代币方式

我们可以允许客户端为令牌交换有效的凭证,而不是在每个请求中都提供诸如用户名和密码之类的凭证。 该令牌使客户端可以访问服务器上的资源。 令牌通常比密码更长,更混乱。 例如,我们要处理的JWT大约为150个字符。 获取令牌后,必须随每个API调用一起发送。 但是,这比通过每个请求发送用户名和密码(甚至通过HTTPS)发送安全性更高。

将令牌视为安全通行证。 您可以在到达时进入受限建筑物的前台(提供用户名和密码)来标识自己,如果可以成功识别出您,则将获得安全通行证。 当您在建筑物中四处走动(通过调用API来尝试访问资源)时,您需要出示通行证,而不是再次进行初始标识过程。

关于智威汤逊

JWT是一个规范草案 ,尽管从本质上讲它们实际上只是已经很普遍的身份验证和授权机制的更具体的实现。 交换令牌的过程。 JWT分为三个部分,各部分之间用句点分隔。 JWT是URL安全的,这意味着它们可以在查询字符串参数中使用。

JWT的第一部分是简单JavaScript对象的编码字符串表示形式,该对象描述令牌以及所使用的哈希算法。 以下示例说明了使用HMAC SHA-256的JWT。

{ 
  "typ" : "JWT",
  "alg" : "HS256" 
}

编码后,对象变为以下字符串:

eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9

JWT的第二部分构成了令牌的核心。 它也代表一个JavaScript对象,其中包含一些信息。 这些字段中的一些是必填字段,而某些是可选字段。 下面显示了一个从规范草案中提取的示例。

{
  "iss": "joe",
  "exp": 1300819380,
  "http://example.com/is_root": true
}

这称为JWT声明集。 为了本文的目的,我们将忽略第三个参数,但是您可以在规范中阅读更多内容。 iss属性是issuer缩写,它指定发出请求的个人或实体。 通常,这将是用户访问API。 exp字段, expires缩写,用于限制令牌的生存期。 编码后,JSON令牌如下所示:

eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ

JWT的第三部分(也是最后一部分)是基于标头(第一部分)和主体(第二部分)生成的签名。 我们的示例JWT的签名如下所示。

dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

生成的完整JWT如下所示:

eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

规范中支持许多其他可选属性。 其中iat表示令牌发行的时间; nbf (不nbf于)表示令牌在特定时间之前不应该被接受; aud (听众)表示令牌预期的接收者。

处理令牌

我们将使用JWT Simple模块来处理令牌,这使我们不必深入研究编码和解码的细节。 如果您真的有兴趣,可以在规范中找到更多信息,或者通读存储库的源代码。

首先使用以下命令安装库。 请记住,可以通过在命令中包含--save标志,将其自动添加到项目的package.json文件中。

npm install jwt-simple

在应用程序的初始化部分,添加以下代码。 此代码导入Express和JWT Simple,并创建一个新的Express应用程序。 该示例的最后一行将名为jwtTokenSecret的应用程序变量设置为值YOUR_SECRET_STRING (确保将此值更改为其他值)。

var express = require('express');
var jwt = require('jwt-simple');
var app = express();

app.set('jwtTokenSecret', 'YOUR_SECRET_STRING');

获取令牌

我们需要做的第一件事是使客户端能够将其用户名和密码交换为令牌。 RESTful API中有两种可能的方法。 第一种是通过向身份验证端点发出POST请求,服务器使用令牌响应成功的请求。 另外,您可以实现一个端点,客户端可以从该端点GET令牌,这要求它们提供其凭据作为查询参数,或者更好的是通过标头提供。

本文的目的是解释身份验证令牌,而不是基本的用户名/密码身份验证机制,因此,假设我们已经具有以下内容,并且已经从请求中获取了usernamepassword

User.findOne({ username: username }, function(err, user) {
  if (err) { 
    // user not found 
    return res.send(401);
  }

  if (!user) {
    // incorrect username
    return res.send(401);
  }

  if (!user.validPassword(password)) {
    // incorrect password
    return res.send(401);
  }

  // User has authenticated OK
  res.send(200);
});

接下来,我们需要使用JWT令牌响应成功的身份验证尝试:

var expires = moment().add('days', 7).valueOf();
var token = jwt.encode({
  iss: user.id,
  exp: expires
}, app.get('jwtTokenSecret'));

res.json({
  token : token,
  expires: expires,
  user: user.toJSON()
});

您会注意到jwt.encode()函数jwt.encode()两个参数。 第一个是将形成令牌主体的对象。 第二个是我们前面定义的秘密字符串。 使用先前描述的issexp字段构造令牌。 注意, Moment.js用于将过期设置为从现在开始的7天。 res.json()方法用于将令牌的JSON表示返回给客户端。

验证令牌

为了验证JWT,我们需要编写一些中间件,该中间件将:

  1. 检查附加令牌。
  2. 尝试对其进行解码。
  3. 检查令牌的有效性。
  4. 如果令牌有效,则检索相应的用户记录并将其附加到请求对象。

让我们开始创建中间件的基础知识:

// @file jwtauth.js

var UserModel = require('../models/user');
var jwt = require('jwt-simple');

module.exports = function(req, res, next) {
  // code goes here
};

为了获得最大的灵活性,我们将允许客户端以以下三种方式之一附加令牌-作为查询字符串参数,表单主体参数或HTTP标头。 对于后者,我们将使用标头x-access-token

这是我们中间件中的代码,它试图检索令牌:

var token = (req.body && req.body.access_token) || (req.query && req.query.access_token) || req.headers['x-access-token'];

请注意,为了访问req.body我们需要首先附加express.bodyParser()中间件。

接下来,让我们尝试解码JWT:

if (token) {
  try {
    var decoded = jwt.decode(token, app.get('jwtTokenSecret'));

    // handle token here

  } catch (err) {
    return next();
  }
} else {
  next();
}

如果解码过程失败,则JWT Simple包将引发异常。 如果发生这种情况,或者没有提供令牌,我们只需调用next()继续处理请求–这仅意味着我们尚未识别用户。 如果存在有效令牌并且已对其进行解码,则我们应该以一个具有两个属性的对象结束-包含用户ID的iss和具有过期时间戳记的exp 。 让我们先检查后者,如果令牌已过期则拒绝它:

if (decoded.exp <= Date.now()) {
  res.end('Access token has expired', 400);
}

如果令牌仍然有效,我们可以检索用户并将其附加到请求对象,如下所示。

User.findOne({ _id: decoded.iss }, function(err, user) {
  req.user = user;
});

最后,将中间件附加到路由:

var jwtauth = require('./jwtauth.js');

app.get('/something', [express.bodyParser(), jwtauth], function(req, res){
  // do something
});

或者,也许将其附加到一系列路线上:

app.all('/api/*', [express.bodyParser(), jwtauth]);

我们的中间件现在检查请求以寻找有效令牌,如果存在,则将用户对象附加到请求。 现在,构建一些简单的中间件来拒绝没有有效令牌的请求应该是相当简单的,尽管您可能希望将其构建到同一块中间件中。

这就是令牌方法的服务器端元素。 在下一节中,我们将研究令牌在客户端的工作方式。

客户端

我们提供了一个简单的GET端点来获取访问令牌。 这很简单,我们可能不需要赘述–只需拨打电话,传递用户名和密码(也许是通过表单)即可,如果请求成功,则将生成的令牌存储在某个地方以备后用。

我们将更详细地介绍将令牌附加到后续调用。 一种方法是使用jQuery的ajaxSetup()方法。 这可以用于直接的Ajax调用,也可以用于在后台使用Ajax与服务器通信的前端框架。 例如,假设我们使用window.localStorage.setItem('token', 'the-long-access-token')将访问令牌放入本地存储中; 我们可以通过标头将令牌附加到所有调用,如下所示:

var token = window.localStorage.getItem('token');

if (token) {
  $.ajaxSetup({
    headers: {
      'x-access-token': token
    }
  });
}

简而言之,这将“劫持”所有Ajax请求,并且如果本地存储中有令牌,它将使用x-access-token标头将其附加到请求。

这不能处理令牌到期,但是应该相对简单。 您会记得,我们返回了带有令牌的到期时间戳。 此外,您可能希望服务器使用指示标头必须重新认证的标头将过期的令牌通知客户端。

与骨干网一起使用

让我们将上一节中的方法应用于Backbone应用程序。 最简单的方法是如下所示全局覆盖Backbone.sync()

// Store "old" sync function
var backboneSync = Backbone.sync

// Now override
Backbone.sync = function (method, model, options) {

  /*
   * "options" represents the options passed to the underlying $.ajax call         
   */
  var token = window.localStorage.getItem('token');

  if (token) {
    options.headers = {
      'x-access-token': token
    }
  }

  // call the original function
  backboneSync(method, model, options);
};

额外的安全性

您可以通过在服务器上存储已发行令牌的记录,然后在每个后续请求中针对该记录进行验证来增加安全性的另一层。 这将防止第三方“欺骗”令牌,并且还允许服务器使令牌无效。 我不会在这里进行介绍,但是它应该相对容易实现。

摘要

在本文中,我们研究了一些在API上进行身份验证的方法,特别是JSON Web令牌。 我们使用带有Express的Node编写了该技术的基本工作实现,并以Backbone为例,介绍了如何在客户端使用它。 GitHub上提供了本文的代码。

我们还没有完全实现规范的更多内容,例如对资源的“声明”,但是我们所做的工作是使用基本建议来建立一种机制,用于在客户端之间交换访问令牌的凭证和JavaScript应用程序的服务器。

当然,您可以将此方法应用于其他技术,例如Ruby或PHP后端,或Ember或AngularJS应用程序。 或者,您可以将其用于移动应用程序。 例如,通过将Web技术与PhoneGap等结合使用,使用诸如Sencha之类的工具或作为完全本机的应用程序。

From: https://www.sitepoint.com/using-json-web-tokens-node-js/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值