使用JSONWebToken在NodeJS和Angular之间进行会话管理

时间:2017-11-10 08:53:10

标签: node.js angular session express json-web-token

我正在尝试构建一个应用程序,使NodeJS作为后端,所有业务逻辑暴露JSON REST服务,由角度4应用程序消耗,这只是一个愚蠢的客户端。到目前为止一切都那么好,但是我很难搞定会话管理。

我发现基于令牌的身份验证是一种方法,因为有一天你可能会为移动应用程序提供服务,但我有一个问题:如果我在服务器端使用JSONWebToken策略,将令牌过期设置为半小时,那么我的客户需要在半小时后重新验证它,这似乎不太合适,因为它可能会强制用户再次登录已经在客户端应用程序上工作,这不是任何Web应用程序的工作方式。我是否还需要在Angular级别维护会话管理并自动登录,如果我的令牌在服务器上过期但是它违反了哑客户端的原则,或者我应该废弃它完全在NodeJS上自己实现会话?另一件事是,如果我实现WebTokenStrategy,我发现对于来自客户端的每个请求,我将前往数据库以验证用户,如果我在NodeJS上进行会话管理,我可以在会话中缓存。

我最难搞清楚的是,我可以在NodeJS上保护我的资源,但是我还需要根据客户端应用程序中的用户权限来提供我的路由和页面,我是否也应该将这些信息存储在NodeJS数据库由相同的API服务器提供服务,但我认为这再次违反了单一责任原则,或者是否应该有另一个数据库用于此客户端站点路由和用户管理。

有人可以建议一个好的方法吗?如果可能的话,可以提供一些例子吗?

感谢。

1 个答案:

答案 0 :(得分:0)

由于您在有效负载上编码所需的信息,因此没有JSON Web令牌不需要访问数据库。但是,如果您希望能够撤销它们,则可以实施redis策略(例如,对于正确的更改)。您的服务器将使用签名部分来确保真实性(感谢您的服务器端JWT秘密)。

您还可以选择所需的到期时间。但如果您想将其限制为30分钟,您还可以实施续订策略。 (在旧令牌即将到期之前请求新令牌:服务器将只提供具有相同数据编码的新令牌。对于前端更新策略,您可以使用这样的库:



'use strict';

/**
 * Helper class to decode and find JWT expiration.
 */
class JwtHelper {

  urlBase64Decode(str) {
    let output = str.replace(/-/g, '+').replace(/_/g, '/');
    switch (output.length % 4) {
      case 0: { break; }
      case 2: { output += '=='; break; }
      case 3: { output += '='; break; }
      default: {
        throw 'Illegal base64url string!';
      }
    }
    return this.b64DecodeUnicode(output);
  }

  // credits for decoder goes to https://github.com/atk
  b64decode(str) {
    let chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
    let output = '';

    str = String(str).replace(/=+$/, '');

    if (str.length % 4 == 1) {
      throw new Error("'atob' failed: The string to be decoded is not correctly encoded.");
    }

    for (
      // initialize result and counters
      let bc = 0, bs, buffer, idx = 0;
      // get next character
      buffer = str.charAt(idx++);
      // character found in table? initialize bit storage and add its ascii value;
      ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer,
        // and if not first of each 4 characters,
        // convert the first 8 bits to one ascii character
        bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0
    ) {
      // try to find character in table (0-63, not found => -1)
      buffer = chars.indexOf(buffer);
    }
    return output;
  }

  // https://developer.mozilla.org/en/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_Unicode_Problem
  b64DecodeUnicode(str) {
    return decodeURIComponent(Array.prototype.map.call(this.b64decode(str), (c) => {
      return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
    }).join(''));
  }

  decodeToken(token) {
    let parts = token.split('.');

    if (parts.length !== 3) {
      throw new Error('JWT must have 3 parts');
    }

    let decoded = this.urlBase64Decode(parts[1]);
    if (!decoded) {
      throw new Error('Cannot decode the token');
    }

    return JSON.parse(decoded);
  }

  getTokenExpirationDate(token) {
    let decoded;
    decoded = this.decodeToken(token);

    if (!decoded.hasOwnProperty('exp')) {
      return null;
    }

    let date = new Date(0); // The 0 here is the key, which sets the date to the epoch
    date.setUTCSeconds(decoded.exp);

    return date;
  }

  isTokenExpired(token, offsetSeconds) {
    let date = this.getTokenExpirationDate(token);
    offsetSeconds = offsetSeconds || 0;

    if (date == null) {
      return false;
    }

    // Token expired?
    return !(date.valueOf() > (new Date().valueOf() + (offsetSeconds * 1000)));
  }
}

const jwtHelper =  new JwtHelper();

const decodedData = jwtHelper.decodeToken('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ');

console.log(decodedData)