Architecture 如何将操作与安全检查分离?

Architecture 如何将操作与安全检查分离?,architecture,Architecture,在我的应用程序中,用户可以执行许多不同的操作,例如(图书/图书馆示例只是为了让它更清楚;我的应用程序中的操作要复杂得多): 租一本书 读书 写书 搬动一本书 查阅一本书 我的应用程序还包含一些安全检查,以防止某些操作。 这些检查与行动没有直接关系,但分别代表与行动不同的概念,例如: 是否允许打开一本书 是否允许进行修改 是否允许看到一本书的存在 目前,不同的操作(租用、阅读等)包含如下代码: void readBook (Book &book) { if (!che

在我的应用程序中,用户可以执行许多不同的操作,例如(图书/图书馆示例只是为了让它更清楚;我的应用程序中的操作要复杂得多):

  • 租一本书
  • 读书
  • 写书
  • 搬动一本书
  • 查阅一本书
我的应用程序还包含一些安全检查,以防止某些操作。 这些检查与行动没有直接关系,但分别代表与行动不同的概念,例如:

  • 是否允许打开一本书
  • 是否允许进行修改
  • 是否允许看到一本书的存在
目前,不同的操作(租用、阅读等)包含如下代码:

void readBook (Book &book)
   {
   if (!checkSecurity (book, OPENBOOK|SEEBOOK)) return;
   ...
   }

void writeBook (Book &book)
   {
   if (!checkSecurity (book, MODIFYBOOK)) return;
   ...
   }
class ISecurityChecker
   {
   public:
      bool isReadBookAllowed() const = 0;
      bool isWriteBookAllowed() const = 0;
      ...
   };
public class Command
{
    public Command(Action<object> act, Func<object, bool> canExecute)
    {
        this.act = act;
        this.canExecute = canExecute;
    }
    private Action<object> act;
    private Func<object, bool> canExecute;
    public void Execute()
    {
        if (CanExecute())
        {
            act(this);
        }
        else
        {
            throw new InvalidOperationException("Command cannot be executed");
        }
    }

    public bool CanExecute()
    {
        if (this.canExecute != null)
        {
            return this.canExecute(this);
        }
        else return true;
    }
}
这种方法很难以动态方式添加新类型的安全检查。例如,动态插件可能会添加额外的安全检查

我目前正在研究的解决方案是,不同的操作调用如下所示的接口:

void readBook (Book &book)
   {
   if (!checkSecurity (book, OPENBOOK|SEEBOOK)) return;
   ...
   }

void writeBook (Book &book)
   {
   if (!checkSecurity (book, MODIFYBOOK)) return;
   ...
   }
class ISecurityChecker
   {
   public:
      bool isReadBookAllowed() const = 0;
      bool isWriteBookAllowed() const = 0;
      ...
   };
public class Command
{
    public Command(Action<object> act, Func<object, bool> canExecute)
    {
        this.act = act;
        this.canExecute = canExecute;
    }
    private Action<object> act;
    private Func<object, bool> canExecute;
    public void Execute()
    {
        if (CanExecute())
        {
            act(this);
        }
        else
        {
            throw new InvalidOperationException("Command cannot be executed");
        }
    }

    public bool CanExecute()
    {
        if (this.canExecute != null)
        {
            return this.canExecute(this);
        }
        else return true;
    }
}
然后,一个复合类实现这个接口。此接口的其他实现可以添加到组合中。这样,插件或应用程序的动态部分就可以在需要时简单地添加新的安全检查

这种方法也有一些缺点。主要问题是接口变得非常大。我将看看是否可以合并一些接口方法(因为它们基本上执行相同或类似的操作),但不确定这是否可行


有没有其他方法可以改进解耦?或者关于如何改进此设计的建议?

我可以建议两种可能的更改。。。还有一条评论

首先,将您的基本检查尽可能向下推链。抛出异常以传递授权错误

void readBook (Book &book)
{
   // readbook doesn't need to perform any checks of it's own.
   // the more primitive actions will scream and yell if the user
   // isn't allowed to perform sub-action X.

   openBook(book);
   BookData d = getBookData(book);

   // etc.
}

void openBook (Book &book)
{
  if (!checkSecurity (book, OPENBOOK))
    throw new SecurityException("User X is not allowed to open this book!");

  // etc.
}

BookData getBookData (Book &book)
{
  if (!checkSecurity (book, SEEBOOK))
    throw new SecurityException("User X is not allowed to read this book's data!");

  // etc.
}
其次,将您的安全措施映射到实际行动。如果愿意,您甚至可以在数据中执行此操作。例如

class Security {

  // this check get tricky.
  // if an "real action" isn't listed anywhere, does the user have implicit permission?
  // (i'm assuming not, for this example.)
  public static Check(String realAction, Boolean requireAll = true) {
    Int32 required = 0;
    Int32 userHas = 0;

    foreach (KeyValuePair<String, List<String>> pair in Actions) {
      if (pair.Value.Contains(realAction))
      {
        required++;
        if (Security.CurrentUser.Actions.Contains(pair.Key))
        {
          userHas++;
        }
      }
    }

    if (requireAll)
    {
      return userHas > 0 && userHas == required;
    }
    else
    {
      return userHas > 0;
    }
  }

  // hardcoded here, but easily populated from a database or config file
  public static Dictionary<String, List<String>> Actions {
    {"OpenBook", new List<String>() { "readBook", "writeBook" }},
    {"SeeBook", new List<String>() { "readBook", "writeBook" }}
  }

}

void readBook(Book &book) {
  if (!Security.Check("readBook")) return false;
  // etc.
}
类安全性{
//这张支票很难兑现。
//如果“实际操作”没有列在任何地方,用户是否具有隐式权限?
//(在这个例子中,我假设不是。)
公共静态检查(字符串realAction,布尔值requireAll=true){
Int32必需=0;
Int32 userHas=0;
foreach(操作中的KeyValuePair对){
if(pair.Value.Contains(realAction))
{
必需++;
if(Security.CurrentUser.Actions.Contains(pair.Key))
{
userHas++;
}
}
}
如果(需要)
{
return userHas>0&&userHas==必需;
}
其他的
{
返回userHas>0;
}
}
//这里是硬编码的,但很容易从数据库或配置文件填充
公共静态字典操作{
{“OpenBook”,新列表(){“readBook”,“writeBook”},
{“SeeBook”,新列表(){“readBook”,“writeBook”}
}
}
无效读本(书和书){
如果(!Security.Check(“readBook”))返回false;
//等等。
}
这里的
Check()
方法接受一个
requireal
参数,但是映射本身也可以很容易地更新为“坚持”或“偏好”,以显示其隐含的“实际操作”


和我的评论:不要过于详细地说明您的安全性。一些安全规则暗示了其他规则,这些规则本身可能毫无意义。例如,
READBOOK
WRITEBOOK
都意味着能够
OPENBOOK
,而
OPENBOOK
本身可能毫无意义。虽然用户能够
OPENBOOK
WRITEBOOK
而不需要
SEEBOOKCOVER
SEEBOOKINSEARCHRESULTS
之类的东西可能看起来很傻,但我建议在书籍阅读时唯一相关的权限是
READBOOK
使用命令查询分离(CQS)方法。一般的想法是定义如下所示的命令类:

void readBook (Book &book)
   {
   if (!checkSecurity (book, OPENBOOK|SEEBOOK)) return;
   ...
   }

void writeBook (Book &book)
   {
   if (!checkSecurity (book, MODIFYBOOK)) return;
   ...
   }
class ISecurityChecker
   {
   public:
      bool isReadBookAllowed() const = 0;
      bool isWriteBookAllowed() const = 0;
      ...
   };
public class Command
{
    public Command(Action<object> act, Func<object, bool> canExecute)
    {
        this.act = act;
        this.canExecute = canExecute;
    }
    private Action<object> act;
    private Func<object, bool> canExecute;
    public void Execute()
    {
        if (CanExecute())
        {
            act(this);
        }
        else
        {
            throw new InvalidOperationException("Command cannot be executed");
        }
    }

    public bool CanExecute()
    {
        if (this.canExecute != null)
        {
            return this.canExecute(this);
        }
        else return true;
    }
}

可能会有更好的CQS实现。这只是一个简单的例子。

是否有某种东西(或一组有限的东西)起到了“网关”的作用:即当用户做某件事时,它总是通过某条路径?例如,在某些应用程序中,用户操作成为http请求,因此可能会在查看http请求的单个瓶颈处应用检查和规则。你有类似的东西吗?@大流士。该应用程序是一个大型模拟应用程序,它在桌面上执行所有操作(因为大量数学计算需要所有数据)。因此,没有中央http网关。我现在尝试添加的是一个中心函数/类/接口/框架,它执行必要的检查(本身不是网关),但问题是我最终得到了一个大矩阵(N个操作x M个可能的安全检查),我尝试看看最好的方法是什么:要么对每个操作执行M个检查,或者让每个检查都有逻辑来检查N个操作中的每一个。第二种方法看起来像我目前正在试验的,将操作作为参数添加到中央检查接口。但是,我没有使用字符串,而是使用单独的方法,这些方法可以由安全检查器实现(如果他们不关心操作,也可以不实现)。您的方法很有趣,因为您通过注册操作和安全检查的组合,在应用程序启动时显式定义了矩阵。谢谢