如何从安全检查中解除操作?

时间:2013-05-07 16:46:14

标签: architecture

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

  • 租一本书
  • 读一本书
  • 写一本书
  • 移动书籍
  • 查找一本书
  • ...

我的应用程序还包含一些安全检查,可以阻止其中一些操作。 这些检查与动作没有直接关系,而是与动作分开表示概念,例如:

  • 是否允许开书
  • 是否允许进行修改
  • 允许看到是否存在书籍

目前,不同的行动(租赁,阅读......)包含如下代码:

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;
      ...
   };

复合类然后实现此接口。可以将此接口的其他实现添加到组合中。这样,应用程序的插件或动态部分可以在需要时添加新的安全检查。

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

任何其他改善去耦的方法?或者关于如何改进这种设计的建议?

2 个答案:

答案 0 :(得分:3)

我可以提出两种可能的改变......以及一条评论。

首先,尽可能向下推动您的原始支票。抛出异常以传达授权错误。

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.
}

这里的Check()方法接受requireAll参数,但是映射本身可以很容易地更新为“坚持”或“更喜欢”出现在他们隐含的“真实行为”中。

我的评论:不要过度详细说明您的安全性。一些安全规则意味着其他规则本身可能毫无意义。例如,READBOOKWRITEBOOK都意味着OPENBOOK的能力,而OPENBOOK本身可能毫无意义。虽然如果用户OPENBOOKWRITEBOOK没有SEEBOOKCOVERSEEBOOKINSEARCHRESULTS之类的内容或者其他任何内容,用户似乎很愚蠢,我建议只有相关的权限在图书阅读时间是READBOOK

答案 1 :(得分:1)

使用命令查询分离(CQS)方法。一般的想法是定义一个这样的命令类:

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;
    }
}

然后在页面中,您使用Command.Execute()而不是常规book.Write / book.Print等。

用法:

Command readBookCommand = new Command(
    k =>
    {
        book.Read();
    },
    l =>
    {
        CanReadBook();
    }
);
readBookCommand.Execute();

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