业务规则引擎_使用业务规则作为授权引擎

本文介绍如何使用Nools业务规则引擎在Node.js应用程序中实现动态授权策略,无需更改源代码即可更新策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在本文中,您将学习如何使用Nools业务规则引擎在Node.js应用程序中做出授权决策。 这样做使您可以更改应用程序的授权策略,而无需更改源代码,从而使更新此类策略更容易。

构建应用程序所需的条件

  • 一个Bluemix帐户
  • HTML,JavaScript和MEAN Web应用程序堆栈知识
  • 可以将Node.js应用程序上载到Bluemix的开发环境,例如Eclipse

应用程序的版本

此应用程序有两个演示版本。 第一个显示具有硬连线授权的应用程序,显示原始的安全策略。 第二部分显示了引入基于业务规则的授权之后的应用程序,该示例说明了更加灵活的安全策略。 请注意,第二个应用程序的安全策略是共享资源,因此无法确定它是否与本文前面的部分相同。

运行原始应用程序 运行修改后的应用程序

在本文中,我将向您展示如何将Node.js应用程序的策略实现为规则库,以及如何为该规则库提供用户界面。 因此,对授权策略的更改变得非常简单,并且不需要程序员参与。

示范应用

我所谓的“世界上最简单的银行”展示了我的应用程序的基于规则的功能。 该银行的系统可通过Internet使用,供银行出纳员和客户使用。 信任用户可以从浏览器窗口顶部的菜单中选择其身份。 然后,浏览器将REST请求发送到服务器,以获取要显示的帐户列表和帐户余额。 服务器决定对用户应该可见的帐户列表,是否应显示这些帐户上的余额,然后将响应与用户可能看到的信息一起发送回去。 浏览器显示该信息。

当前,在两种情况下,authorize函数返回true:

  • 如果主题(应用程序用户)的角色是Teller,并且主题的分支与对象(帐户)的分支相同。 这样,柜员就可以查看其分支机构中的所有帐户并告诉客户其余额。
  • 如果主题的角色是客户,并且主题与对象相同。 这使客户可以查看自己的余额。
var authorizeAction = function(subject, verb, object) {
  // Get additional information
  var subjectInfo = users[subject];
  var objectInfo = users[object];

  // Let customers see their own balance, and tellers
  // the balances of everybody in their branch.
  if (subjectInfo.role == "Teller")
    return subjectInfo.branch == objectInfo.branch;
  else if (subjectInfo.role == "Customer")
    return subject == object;

  // If no rule allows access, deny it.
  return false;
};

由于应用程序只有一个动词来显示帐户余额,因此authorize函数会忽略该动词。

步骤1.开始使用Nools规则引擎

因为在此示例中实施的策略非常简单,所以我选择使用Nools,它似乎是Node.js最受欢迎的规则引擎(规则引擎通常以库的形式实现,这使它们特定于语言)。 您可以在Nools页面上了解有关此规则引擎的更多信息。

  1. 要使规则引擎可用,请在package.json文件的dependencies部分添加"nools": "*"
  2. 将以下代码添加到app.js文件中以使用规则引擎。 在这一点上,它实际上什么也没做。
    // Use the Nools library
    var nools = require('nools');
    
    // Create a new flow, a rule base.
    // For now, keep the rule base empty.
    var flow = nools.flow("authz", function(flow) {
      ;
    });
    
    // Create a new session. A session combines
    // a rule base with facts to arrive at a decision.
    var session = flow.getSession();
    
    // Add facts to the session
    session.assert("Hello");
    session.assert("Goodbye");
    
    // Attempt to use the rule base
    session.match().then(
      function() {
        console.log("Successfully ran the flow");
      },
      function(err) {
        console.log("Error" + err);
      }
    );
    
    // Dispose of the session, delete all the facts to make it
    // usable in the future
    session.dispose();
  3. 推送应用程序(上载并运行)。
  4. 查看日志文件。 如果使用的是Eclipse,则在控制台窗口中。 如果使用cf命令行界面,请运行以下命令:
    cf logs <application name> --recent
  5. 验证您是否看到成功消息( Success in running the flow )。

步骤2.创建对象类

Nools用于评估规则或作为结论的事实可以存储在对象中。 对于授权决策,最简单的有两个类:

  • AuthzRequest的请求信息(包括任何相关的附加信息)
  • AuthzResponse用于响应

将以下代码添加到app.js文件中,并将其放在步骤1的代码之前。

// Object class for authorization request
var AuthzRequest = function(subject, verb, object) {
  this.subect = subject;
  this.verb = verb;
  this.object = object;
};


// Object class for authorization response
var AuthzResponse = function(answer) {
  this.answer = answer;
}

使用标准JavaScript机制定义对象类,JavaScript机制是一种填充必要字段的构造函数。

步骤3.创建一个许可流

对于此步骤,创建许可流并将其激发。

  1. 修改flow变量的定义以向flow添加许可规则:
    // Create a new flow, a rule base.
    // Be permissive
    var flow = nools.flow("authz", function(flow) {
    
      this.rule("Permissive", // Rule name
    
      // The facts on which the rule operates. If the list
      // for a fact has two items, the first is the object class
      // and the second is the variable name. If it has three, the
      // the third is a condition that has to evaluate to true for the
      // rule to be applied.
      //
      // When there is only one fact, it can be in an un-nested list,
      // the nested list here is just for illustration of the general
      // case with multiple facts.
        [[AuthzRequest, "req"]],
    
    
        // The function to call if the rule is fulfilled
        function(facts) {
    
          // The parameter contains the facts.
          
          // Prove we got the parameter
          console.log(facts.req.verb);
          
          // Always allow
          this.assert(new AuthzResponse(true));
        }
      );
    });
  2. 将两个对session.assert现有调用替换为包含AuthzRequest一个。
    // Add an AuthzRequest fact to the session
    session.assert(new AuthzRequest("subject", "verb", "object"));
  3. session.match调用之后,添加一个请求以读取结果:
    console.log(session.getFacts(AuthzResponse));
  4. 推送代码并查看日志。 验证您是否获得了与此类似的一行:
    2015-05-27T20:20:50.64-0500 [App/0]      OUT [ { answer: true } ]

步骤4.修改authorizeAction函数以调用规则引擎

  1. 删除app.js文件中所有引用session变量的调用。 他们在那里是为了帮助您了解规则引擎,并且不再需要它们。
  2. 用以下代码替换authorizeAction函数:
    // The function that actually authorizes a user (subject)
    // to do something, such as view the balance (verb)
    // of an account (object).
    var authorizeAction = function(subject, verb, object) {
      // Get additional information
      var subjectInfo = users[subject];
      var objectInfo = users[object];
    
      // Add the names to the information to make it easier
      // to use the rule base.
      subjectInfo.name = subject;
      objectInfo.name = object;
    
      // Create a new session. A session combines
      // a rule base with facts to arrive at a decision.
      var session = flow.getSession();
    
      // Add an AuthzRequest fact to the session
      session.assert(new AuthzRequest(subjectInfo, verb, objectInfo));
    
      // Call the flow for a decision
      session.match().then(
        function() {
          console.log("Successfully ran the flow");
        },
        function(err) {
          console.log("Error" + err);
        }
      );
    
      // Get the decision. session.getFacts(<type>) gets all the
      // facts of that type. In this case, there would be one
      // AuthzResponse.
      var resultList = session.getFacts(AuthzResponse);
      var decision;
    
      if (resultList.length == 0)
        // There would be no AuthzResponse if no rule triggered.
        // If no rule permits an action, it is denied.
        decision = false;
      else
        decision = resultList[0].answer;
    
      // Dispose of the session, delete all the facts to make it
      // usable in the future
      session.dispose();
    
      return decision;
    };
  3. 推送应用程序。
  4. 使用该应用程序。 确保无论您选择哪个用户,您都可以查看所有帐户。

步骤5.返回原始政策

要返回原始策略,请将nools.flow调用替换为该策略中包含规则的调用:

// Create a new flow, a rule base.
var flow = nools.flow("authz", function(flow) {

  this.rule("Teller", // Rule name

  // Notice the added third member of the list, to restrict
  // this rule to cases where the subject is a teller.
    [[AuthzRequest, "req", "req.subject.role=='Teller'"]],


    // The function to call if the rule is fulfilled
    function(facts) {
      // Allow if the subject and object share the
      // same branch.
      this.assert(new AuthzResponse(
        facts.req.subject.branch == facts.req.object.branch));
    }
  );

  this.rule("Customer", // Rule name

  // Notice the added third member of the list, to restrict
  // this rule to cases where the subject is a customer.
    [[AuthzRequest, "req", "req.subject.role=='Customer'"]],


    // The function to call if the rule is fulfilled
    function(facts) {
      // Allow if the subject and object have the same name,
      // let the customer see his/her own balance.
      this.assert(new AuthzResponse(
        facts.req.subject.name == facts.req.object.name));
    }
  );

});

步骤6.将策略存储在一个对象中

到目前为止,您已经用执行相同操作的更长的代码替换了几行简单的代码。 但是,创建不必要的钝性代码并不是我们的真正目的。 目的是创建一个人们可以在不更改源代码的情况下进行编辑的策略。

有两种方法可以完成此操作。 第一种是使用Nools自己的DSL (域特定语言)。 但是,该语言非常灵活,因此非常复杂。 它不适合使非程序员可以更改策略的目的。

第二种方法是将整个策略放在JavaScript对象中,然后提供用于修改该对象的用户界面。 这需要应用程序本身所需的相同类型的编程专业知识。

有多种表达规则的方式。 对于此特定应用程序,授权请求始终具有相同的六个变量:

  • subject.name
  • subject.role
  • subject.branch
  • object.name
  • object.role
  • object.branch

因为变量很少,所以指定规则的最简单方法是指定授权操作所需的变量值。 这些值可以是常数(例如, subject.role等于Teller ),另一个变量( subject.branch等于object.branch ),或一个特殊的值意味着特定变量无所谓在该规则。

  1. 为安全策略添加一个对象:
    // Security policy. The policy includes three parameters:
    //
    // Vars is the variables that make up the policy.
    // Constants are the constants that may appear in the policy.
    // (note, these are only required for the user interface)
    //
    // Rules are the actual rules. Each rule contains variables
    // (enclosed in quotes to allow for dots within a variable name)
    // and the values they need to match. They can be matched against
    // constants or other variables. If the value of a variable does
    // not matter for the rule, it does not appear in that rule.
    //
    // The rules are all permits. If a request does not match any rules,
    // it is denied.
    var secPolicy = {
      vars: ["subject.name", "subject.role", "subject.branch",
              "object.name", "object.role", "object.branch" ],
      constants: ["Teller", "Customer", "Austin", "Boston"],
      rules: [
        {   // The teller rule
          "subject.role": {type: "constant", value: "Teller"},
          "subject.branch": {type: "variable", value: "object.branch"}
        },
        {  // The customer rule
          "subject.role": {type: "constant", value: "Customer"},
          "subject.name": {type: "variable", value: "object.name"}
        }
      ]
    };
  2. 处理规则非常复杂,因此可以使用外部功能。 不幸的是,在Nools模式中,您只能访问事实。 因此,要将函数置入事实,请创建一个函数对象类:
    // Object class for functions, so they will be
    // usable as "facts" within Nools
    var FunObj = function(name, fun) {
      this.name = name;
      this.fun = fun;
    }
  3. authorizeAction函数中,使用matchRuleRequest函数声明一个新事实:
    // Add a necessary function as a "fact"
      session.assert(new FunObj("matchRuleRequest", matchRuleRequest));
  4. 添加实际的matchRuleRequest函数和它使用的实用程序函数:
    // Get a value in a request from a rule style variable name
    var getRequest = function(request, varName) {
      // The outer and inner variable names in the request
      // The rule has rule["subject.role"],
      // but the AuthorizationRequest has
      // request["subject"]["role"] for that
      reqVarNames = varName.split(".");
    
      // The value in the request value
      return request[reqVarNames[0]][reqVarNames[1]];
    }
    
    
    // Check if an authorization request matches a rule
    var matchRuleRequest = function(ruleNumber, request) {
      var rule = secPolicy.rules[ruleNumber];
    
      for (variable in rule) {
        // Get the value
        var ruleValue = rule[variable];
    
        // If it is a constant, check equality to that constant
        if (ruleValue.type == "constant" &&
          ruleValue.value != getRequest(request, variable))
            return false;
    
        // If it is a variable, get the value in that variable
        // and compare
        if (ruleValue.type == "variable" &&
          getRequest(request, ruleValue.value)
          != getRequest(request, variable))
            return false;
      }
    
      // If we get here then there are no mismatches.
      return true;
    };
  5. 修改创建流程以使用策略的功能。
    // Create a new flow, a rule base.
    var flow = nools.flow("authz", function(flow) {
    
      // Create rules from the policy
      for(var i=0; i<secPolicy.rules.length; i++) {
        this.rule("Rule #" + i,   // Rule name
          [
            // Find two facts, each with a pattern. The first fact,
            // match, just makes the matchRuleRequest function available
            // if the context of the matching pattern for the second
            // function, which checks if an rule matches the 
            // authorization request.
            [FunObj, "match", "match.name == 'matchRuleRequest'"],
            [AuthzRequest, "req", "match.fun(" + i + ", req)"]
          ],
          function(facts) {
            // If we get here, the rule matches, so allow
            this.assert(new AuthzResponse(true));
          }
        );
      }
    });
  6. 推送并验证应用程序是否仍然遵循安全策略。

步骤7.为策略创建用户界面

最后,要允许不是程序员的管理员修改策略,您需要为其创建用户界面。 您可以在源代码中看到安全策略界面的文件public / policy.html和public / scripts / policy.js。 他们以相当标准的方式使用Angular,因此我不会太深入地研究它们。 要了解有关Angular的更多信息,请参阅developerWorks上的“ 使用Bluemix和MEAN堆栈构建自发布Facebook应用程序 ”系列。

有趣的一点是HTML select标记最适合字符串值。 因此,浏览器上的策略不是将参数值标识为对象(例如{type: "constant", value: "Teller"} ),而是使用带有前缀的字符串来确定该值是变量还是常量(例如c:Teller )。 该策略是使用REST传输的,在“使用Bluemix和MEAN堆栈构建自发布Facebook应用程序”系列中也对REST的用法进行了说明。

另一个问题是演示应用程序无法将策略保存在任何地方。 它只是将其保存在内存中。 这意味着重新启动应用程序时该策略将丢失。 实际应用程序通常可以访问数据库(例如MongoDB),并将策略存储在该数据库中。 刚刚提到的系列还介绍了如何使用MongoDB。

您现在可以在http://world-silliest-bank-after.mybluemix.net/上使用最终应用程序。 请注意,安全策略是全局的,因此,如果它似乎是随机更改的,则可能是因为其他人正在同时更改它。 您有一个按钮可以下载policy.html文件中的当前安全策略以检查这种可能性。

结论

在本文中,为了重点关注授权引擎这一重要主题,我使用了一个非常简单的应用程序,因此它具有非常简单的安全策略。 对于如此简单的应用程序和安全策略,诸如Nools之类的规则引擎实在是太过分了。 但是,实际的应用程序要复杂得多。 使用业务规则方法,授权引擎可以从第三方服务器收集其他信息(事实),并检查值是否高于阈值或用户是否为特定组的成员。

要开始使用真实的应用程序,请考虑授权需求:

  • 谁是使用者?使用者? 关于用户的哪些信息可能与授权决策有关?
  • 动词需要授权哪些动作?
  • 对于这些操作中的每一个,它影响的对象类型是什么? 这些对象的哪些属性可能与授权决策有关?

在确定了决策所需的信息以及如何获取信息之后,下一步就是弄清楚用户界面。 您是否只需要检查变量是否相等(如本文所述)? 您是否需要检查变量是否是组的成员,还是高于或低于阈值? 您将如何以非技术用户可以理解的方式展示各种选项,以便他们无需程序员参与即可更改授权策略?


翻译自: https://www.ibm.com/developerworks/security/library/se-bluemix-business-rules-authorization-engine-app/index.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值