2021-07-12-带着问题看源码2-NodeRed的用户认证机制是怎样的

1. 几种常用的认证机制#

1.1. HTTP Basic Auth#

Basic Auth是开放平台的两种认证方式,简单点说明就是每次请求API时都提供用户的username和password。

  • 优点:
    • 使用非常简单,
    • 开发和调试工作简单,
    • 没有复杂的页面跳转逻辑和交互过程;
    • 更利于发起方控制;
  • 缺点:
    • 安全性低,每次都需要传递用户名和密码,用户名和密码很大程度上存在被监听盗取的可能;一次密码盗用,会导致所有使用此密码的全部应用处于风险之中
    • 同时应用本地还需要保存用户名和密码,在应用本身的安全性来说,也存在很大问题;
    • 开放平台服务商出于自身安全性的考虑(第三方可以得到该服务商用户的账号密码,对于服务商来说是一种安全隐患),未来也会限制此认证方式(Twitter就计划在6月份停止Basic Auth的支持)
    • 用户如果更改了用户名和密码,还需要重新进行密码校验的过程。

      1.2. OAuth#

      OAuth是一个关于授权(authorization)的开放网络标准,在全世界得到广泛应用,目前的版本是2.0版。
      OAuth在”客户端”与”服务提供商”之间,设置了一个授权层(authorization layer)。”客户端”不能直接登录”服务提供商”,只能登录授权层,以此将用户与客户端区分开来。”客户端”登录授权层所用的token(token),与用户的密码不同。用户可以在登录的时候,指定授权层token的权限范围和有效期。
      “客户端”登录授权层以后,”服务提供商”根据token的权限范围和有效期,向”客户端”开放用户储存的资料。
      客户端必须得到用户的授权(authorization grant),才能获得token(access token)。
      主要流程如下:
      主要流程)

2. NodeRed中的认证机制使用#

NodeRed使用 OAuth 2.0 实现认证

2.1. 基于用户名/密码凭据的身份验证#

要在编辑器和管理API上启用用户身份验证,在settings.js文件中取消adminAuth属性的注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
adminAuth: {
type: "credentials",
users: [
{
username: "admin",
password: "$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN.",
permissions: "*"
},
{
username: "george",
password: "$2b$08$wuAqPiKJlVN27eF5qJp.RuQYuy6ZYONW7a/UWYxDTtwKFCdB8F19y",
permissions: "read"
}
]
}

users属性是一个user对象数组。这允许您定义多个用户,每个用户可以拥有不同的权限。
上面的示例配置定义了两个用户。一个叫admin的人可以在编辑器内做任何事情,并且有一个密码为password的密码。另一个叫george,他被赋予只读访问权限。
请注意,密码是使用bcrypt算法生成的。

2.2. 针对任何OAuth/OpenID提供者(如Twitter或GitHub)进行身份验证#

要使用外部身份验证源,Node-RED可以使用Passport提供的各种策略。
Node-RED认证模块可以用于Twitter和GitHub。他们总结了一些具体的策略细节,使其更易于使用。但它们也可以用作其他类似策略的身份验证模板。
下面的示例演示如何配置针对Twitter的身份验证,而不使用我们提供的auth模块。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
adminAuth: {
type:"strategy",
strategy: {
name: "twitter",
label: 'Sign in with Twitter',
icon:"fa-twitter",
strategy: require("passport-twitter").Strategy,
options: {
consumerKey: TWITTER_APP_CONSUMER_KEY,
consumerSecret: TWITTER_APP_CONSUMER_SECRET,
callbackURL: "http://example.com/auth/strategy/callback",
verify: function(token, tokenSecret, profile, done) {
done(null, profile);
}
},
},
users: [
{ username: "knolleary",permissions: ["*"]}
]
};

2.3. 自定义用户身份验证#

除了将用户硬编码到设置文件中,还可以插入自定义代码对用户进行身份验证。这使得与现有的身份验证方案集成成为可能。

  1. 将以下内容保存到一个名为/user-authentication.js的文件中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    module.exports = {
    type: "credentials",
    users: function(username) {
    return new Promise(function(resolve) {
    // Do whatever work is needed to check username is a valid
    // user.
    if (valid) {
    // Resolve with the user object. It must contain
    // properties 'username' and 'permissions'
    var user = { username: "admin", permissions: "*" };
    resolve(user);
    } else {
    // Resolve with null to indicate this user does not exist
    resolve(null);
    }
    });
    },
    authenticate: function(username,password) {
    return new Promise(function(resolve) {
    // Do whatever work is needed to validate the username/password
    // combination.
    if (valid) {
    // Resolve with the user object. Equivalent to having
    // called users(username);
    var user = { username: "admin", permissions: "*" };
    resolve(user);
    } else {
    // Resolve with null to indicate the username/password pair
    // were not valid.
    resolve(null);
    }
    });
    },
    default: function() {
    return new Promise(function(resolve) {
    // Resolve with the user object for the default user.
    // If no default user exists, resolve with null.
    resolve({anonymous: true, permissions:"read"});
    });
    }
    }
  2. 在settings.js中设置adminAuth属性来加载这个模块:

    1
    adminAuth: require("./user-authentication")

    2.4. 自定义的身份验证token#

    在某些情况下,您可能需要使用自己的身份验证token,而不使用Node-RED生成的身份验证token。例如:

  • 您希望使用基于OAuth的用户身份验证,但是您还需要自动访问管理API,它不能执行OAuth要求的交互式身份验证步骤
  • 您希望将Node-RED集成到现有系统中,其中用户已经登录,并且不希望他们在访问编辑器时再次登录

adminAuth可以包括一个token函数。如果对管理api的请求不包含Node-RED识别为自己的身份验证token,则将调用此函数。将请求中提供的token传递给它,它应该返回一个与经过身份验证的用户进行解析的承诺,如果token无效,则返回null。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
adminAuth: {
...
tokens: function(token) {
return new Promise(function(resolve, reject) {
// Do whatever work is needed to check token is valid
if (valid) {
// Resolve with the user object. It must contain
// properties 'username' and 'permissions'
var user = { username: 'admin', permissions: '*' };
resolve(user);
} else {
// Resolve with null as this user does not exist
resolve(null);
}
});
},
...
}

默认情况下,它将使用授权http头,并期待一个Bearer类型token——只将token的值传递给函数。如果它不是Bearer类型token,则授权头的完整值将传递给函数,其中包含类型和值。
要使用不同的HTTP头,可以使用tokenHeader设置来确定使用哪个头:

1
2
3
4
5
6
7
adminAuth: {
...
tokens: function(token) {
...
},
tokenHeader: "x-my-custom-token"
}

2.5. 使用自定义token访问编辑器#

要在不提示登录的情况下使用自定义Token访问编辑器,请向URL添加?access_token=< access_token >。编辑器将在本地存储该Token,并将其用于所有未来的请求。

3. 功能实现的参与者#

3.1. oauth2orize(主要功能及在本模块中的意义)#

OAuth2orize 是 NodeJS 的授权服务器工具包。它提供了一套中间件, 这些中间件与 passport 身份验证策略和特定于应用程序的路由处理程序相结合, 可用于组装实现 OAuth 2.0 协议的服务器。

主要方法:

  • oauth2orize.createServer
    提供授权服务器
  • server.exchange
    授权码交换访问Token。用户名及密码验证通过后,第三方(浏览器)可以使用授权码换到访问Token,通过Token就可以访问服务器资源。

3.2. passport#

passport.js是Nodejs中的一个做登录验证的中间件,极其灵活和模块化,并且可与Express、Sails等Web框架无缝集成。passport模块本身不能做认证,所有的认证方法都以策略模式封装为插件,需要某种认证时将其添加到package.json即可。

主要方法:

  • passport.use
    提供策略及配置
  • passport.authenticate
    对请求进行身份验证并指定认证策略
  • passport.initialize
    在基于Connect或express的应用程序中,需要Passport. initialize()中间件来初始化Passport。
  • passport.session
    如果您的应用程序使用持久登录会话,还必须使用passport.session()中间件。
    请注意,启用会话支持是完全可选的,尽管大多数应用程序都推荐启用会话支持。如果启用,请确保在passport.session()之前使用session(),以确保登录会话以正确的顺序恢复。

3.3. express-session#

session 的运作通过一个 session_id 来进行。session_id 通常是存放在客户端的 cookie 中,比如在 express 中,默认是 connect.sid 这个字段,当请求到来时,服务端检查 cookie 中保存的 session_id 并通过这个 session_id 与服务器端的 session data 关联起来,进行数据的保存和修改。
express 中操作 session 要用到 express-session (https://github.com/expressjs/session ) 这个模块,主要的方法就是session(options),其中 options 中包含可选参数,主要有:

  • name: 设置 cookie 中,保存 session 的字段名称,默认为 connect.sid 。
  • store: session 的存储方式,默认存放在内存中,也可以使用 redis,mongodb 等。express 生态中都有相应模块的支持。
  • secret: 通过设置的 secret 字符串,来计算 hash 值并放在 cookie 中,使产生的 signedCookie 防篡改。
  • cookie: 设置存放 session id 的 cookie 的相关选项,默认为
    • (default: { path: ‘/‘, httpOnly: true, secure: false, maxAge: null })
  • genid: 产生一个新的 session_id 时,所使用的函数, 默认使用 uid2 这个 npm 包。
  • rolling: 每个请求都重新设置一个 cookie,默认为 false。
  • resave: 即使 session 没有被修改,也保存 session 值,默认为 true。

3.4. passport-http-bearer#

Passport的HTTP Bearer认证策略。

这个模块允许您在Node.js应用程序中使用Bearer Token(由RFC 6750指定)验证HTTP请求。Bearer Token通常使用保护API端点,并且通常使用OAuth 2.0发布。

通过插入Passport,BearerToken支持可以轻松地集成到支持连接式中间件的任何应用程序或框架中,包括Express。

passport-oauth2-client-password 策略#

OAuth 2.0 Passport客户端密码认证策略。

这个模块允许您验证请求体中包含客户端凭据的请求,正如OAuth 2.0规范所定义的那样。这些凭据通常用于保护Token端点,并用作HTTP基本身份验证的替代方案。

4. 功能分阶段描述#

auth:packages/node_modules/@node-red/editor-api/lib/auth/index.js
auth_users:packages/node_modules/@node-red/editor-api/lib/auth/users.js
auth_tokens:packages/node_modules/@node-red/editor-api/lib/auth/tokens.js
auth_strategies:packages/node_modules/@node-red/editor-api/lib/auth/strategies.js
storage:packages/node_modules/@node-red/runtime/lib/storage/index.js

%% 配置
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#FFFFFF'}}}%%
sequenceDiagram
autonumber

%%实现
title: NodeRed中的用户认证机制(settings=credentials)

participant 其他
auth -->> auth: 配置passport认证策略
auth -->> auth: 创建 oauth2 认证服务器
auth -->> auth: 配置 oauth2 授权码交换访问Token处理
其他 -->> auth: 启动初始化流程init
auth -->> auth_users: 启动初始化流程init,初始化 Users 模块
auth -->> auth_tokens: 启动初始化流程init,初始化 Tokens 模块
其他 -->> auth: 将 /auth/login 路径路由到 login 、 errorHandler 方法 
其他 -->> auth: 将 /auth/token 路径路由到 ensureClientSecret 、 authenticateClient 、 getToken、 errorHandler 方法
其他 -->> auth: 将 /auth/revoke 路径路由到  needsPermission 、revoke、 errorHandler 方法
auth -->> auth: ...

其他 -->> auth: 用户登陆操作,调用login方法,根据不同的settings配置,返回显示界面
auth -->> auth: ...

其他 -->> auth: 用户输入用户名称和密码,进行认证
auth -->> auth_users: 验证客户端ID(node-red-editor)是否有效,auth_users 使用 oauth2-client-password 策略
auth -->> auth: ...

auth -->> auth_strategies: 根据已配置的 oauth2 授权码交换访问Token方法,获取Token
auth_strategies -->> auth_users : 用户认证 authenticate(判断用户是否为合法用户,验证用户名和密码)
auth_users -->> auth_strategies: 返回用户信息信息
auth_strategies -->> auth_strategies: 判断用户权限, permissions.hasPermission
auth_strategies -->> auth_strategies: 创建Token
auth_strategies -->> auth_tokens : 创建会话信息,设置会话结束时间,做Token超时处理
auth_tokens -->> storage: 保存会话信息到文件(/home/freeman/.node-red/.sessions.json)
auth -->> auth: ...

其他 -->> auth: 用户登陆后所有操作,调用 bearerStrategy 方法,判断请求所携带的Token是否有效,若无效则返回错误信息,禁止操作
auth -->> auth: ...

其他 -->> auth: 用户登出操作,调用 revoke 方法
auth -->> auth: ...

5. 功能使用场景#

存在于使用的各阶段(初始化、运行、结束),是开启用户验证后必需的功能。

6. 实现方式特点#

  1. 使用策略模式对算法封装
  2. 模块化使用第三方库,完成用户认证
  3. 可扩展实现定制的用户认证

2021-07-12-带着问题看源码2-NodeRed的用户认证机制是怎样的

https://blog.buqia.fun/2022/02/17/2021-07-12-带着问题看源码2-NodeRed的用户认证机制是怎样的/

作者

lxmuyu

发布于

2022-02-17

更新于

2022-03-11

许可协议