【问题标题】:Class hierarchy - class should only called by another class类层次结构 - 类只能由另一个类调用
【发布时间】:2017-09-14 10:52:05
【问题描述】:

我尝试实现一个Security 类和一个Secret 类。在我的整个项目中,Secret 类只能由Security.getSecretInstance().doSomeSecretAction() 调用

所以Secret.doSomeSecretAction() 应该会抛出编译错误。

我需要Security.getSecretInstance() 来进行身份验证过程。

我正在寻找一个好的模式或其他东西,但我认为我的搜索关键字太糟糕了,或者我的要求是愚蠢的/或不可能的。

目前我调用Security.getSecretInstance() 它返回Secret 的单例实例,但我也可以调用Secret.doSomeSecretAction()。没有区别。

你有什么模式、关键字或sn-ps给我吗?

编辑 我对真棒的定义是我有这样一种方法:

Security.isAuthorized { secret in
   secret.doSomeSecretAction
 }, failure { 
   print("permission denied")
}

而我只能通过这个.isAuthorized-Method 获得秘密

【问题讨论】:

  • 肯定有一些方法可以(大致)实现您在此处描述的内容(doSomeSecretAction() 不应该是类方法,但除此之外它很好)。但我想明确一点:您希望这样做是为了防止开发人员在不应该的情况下意外调用.doSomeSecretAction,而不是阻止有权访问您的框架并打算提取秘密的人,对吗?前者相当简单。后者是不可能的。没有办法在 Secret 中隐藏开发人员无法提取的任何内容。这不是 Swift 的限制。它不能用任何语言完成。
  • 您完全理解我的意思:是的,我希望这样做是为了防止开发人员在不应该调用.doSomeSecretAction 时意外调用。

标签: swift class security hierarchy


【解决方案1】:

我建议做的是将Secret 嵌套在Security 中,将Secret 设为私有并在Security 中创建可以访问Secret 的非私有方法。像这样的:

class Security {

    class func doSomeSecretAction() {
        Secret.doSomeSecretAction()
    }

    private class Secret {
        class func doSomeSecretAction(){
            print("Private method called")
        }
    }
}

Security.doSomeSecretAction()

这里,Security.doSomeSecretAction() 可以从 Security 类外部调用,但 Secret.doSomeSecretAction() 只能在 Security 类内部调用。

基于 cmets 的更新: 一个可行的解决方案是声明Security private 的初始化器,因此它只能从Security 类内部调用并声明一个计算变量(现在我称之为shared),这是唯一的访问点初始化器。此计算变量返回nil 或基于Security.isAuthorizedSecret 类的新实例。这样,每次调用Secret的函数时,都会检查授权状态,只有授权状态才能调用该函数,否则shared变量返回nil,因此不会调用该方法。

class Security {

    static var isAuthorized = false //change this when the authorisation status changes

    class Secret {

        static var shared: Secret? {
            if Security.isAuthorized {
                return Secret()
            } else {
                return nil
            }
        }

        private init(){} //a new instance of Secret can only be created using the `shared` computed variable, the initializer cannot be called directly from outside the Secret class

        func doSomeSecretAction(){
            print("Private method called")
        }
    }
}

Security.Secret.shared //nil
//Security.Secret.init() //if you uncomment this line, you'll get an error saying all initializers are inaccessible
Security.Secret.shared?.doSomeSecretAction() //nil

Security.isAuthorized = true
Security.Secret.shared?.doSomeSecretAction() //function is called

Security.isAuthorized = false
Security.Secret.shared?.doSomeSecretAction() //nil

【讨论】:

  • 很抱歉,但我不相信。我的 Secret 类有 500 行代码和很多方法。使用您的模式/方法,我复制了代码。没关系,但我希望有一个更优雅的模式。
  • 为什么要复制该代码?您仍然只在 Secret 类中编写该代码,您只需要为您希望能够从外部 Security 使用的那些创建辅助函数。而且,函数内部在Secret中的时间长短也没有关系,辅助函数只需要一行,你可以在其中调用Secret中的私有函数。
  • 你试图做的事情,Security.getSecretInstance().doSomeSecretAction() 可以工作,但 Secret.doSomeSecretAction() 不可调用不能工作,因为你不能确保一个函数只能从另一个函数内部调用,如果函数本身对外部范围可见。您只能通过声明他的函数或整个类具有受限访问权限并创建可以直接访问它的辅助函数来确保这一点。
  • 对于Secret 中的每个方法,我是否需要在Security 中编写3 行代码?在我被允许调用 Secret 中的方法之前,有时会进行异步身份验证检查。因此,对于每种方法,我都会得到冗余代码。我对真棒的定义是我有一种这样的方法:Security.isAuthorized { secret secret.doSomeSecretAction }, failure { print("permission denied") } 我只能用这个.isAuthorizedmethods 得到秘密
  • 您刚才在评论中描述的内容与您在问题中提出的内容大不相同。您在问题中提出的问题是不可能的,因为一旦您在 Security 类的范围之外返回 Secret 的单例实例,您就不能再将其声明为私有,因此可以从 @ 之外的任何地方访问它987654352@ 类也是如此。
【解决方案2】:

大卫正在编辑他的答案时,我正在研究这个答案;我没有意识到他不久前发布了更新。我们的答案有很多重叠之处,所以这只是相同方法的另一种风格。

首先,我想明确的是,您所描述的只能实现封装,而不是“安全”。我的意思是,您可以构建一个系统,让开发人员可以轻松正确地使用它,而难以错误地使用它。这很简单。但是您将无法阻止开发人员提取秘密并运行他们想要的任何代码。这是他们的机器,你给他们代码。他们总是可以运行它。他们有一个调试器;你不会隐藏任何东西。

但是,防止意外误用是一个很好的目标,而且非常简单。第一件事是您应该使用实例方法,而不是类方法。类方法使这一切变得比它需要的更难。您的问题的解决方案看起来像这样,大部分访问控制依赖于fileprivate

class Security {
    enum Error: Swift.Error {
        case unauthorized
    }

    // This feels like it should be nested in Security, but doesn't have to be
    class Secret {
        // No one outside this file can instantiate one of these. It's likely
        // that you'll be passing some parameters here of course.
        fileprivate init() {}

        // I'm assuming you want these to be single use, so people can't store
        // a reference to them an reuse them. This is one simple way. 
        fileprivate var isAuthorized = true

        private func validate() {
            // I'm treating this kind of reuse as a programming error and
            // crashing. You could throw if you wanted, but it feels like it
            // should never happen given your design.
            guard isAuthorized else { 
                fatalError("Secrets can only be used once") 
            }
        }

        func doSomeSecretAction() {
            // Every "protected" method (which may be all of them) needs to
            // call validate() before running.
            validate()
            print("SECRET!")
        }
    }

    // Public so we can test; obviously this would at least private(set)
    var isAuthorized = false 

    func withAuthorization(execute: (Secret) -> Void) throws {
        guard isAuthorized else { throw Error.unauthorized }

        // We create a new Secret for every access and invalidate it after.
        // That prevents them from being stored and reused.
        let secret = Secret()
        execute(secret)
        secret.isAuthorized = false
    }
}

// -- Some other file

let security = Security()

security.isAuthorized = true // For testing

var stealingTheSecret: Security.Secret?

do {
    try security.withAuthorization {
        $0.doSomeSecretAction() // This is good
        stealingTheSecret = $0 // Try to steal it for later use
    }
} catch Security.Error.unauthorized {
    print("Unauthorized")
}

stealingTheSecret?.doSomeSecretAction() // Let's use it: Crash!

原则上,您可以通过使用UnsafeMutablePointer 直接为Secret 分配内存并在最后销毁它来摆脱validate() 样板,但这可能比避免多一行代码。

(请注意,您自己分配内存仍然不能保护您免受调用者保存对象的影响;他们总是可以复制内存并使用.load 重新实例化它;您可以做任何不安全的事情,所以调用者可以。这也允许他们通过直接修改布尔值或在使对象无效之前复制对象来规避validate()。没有技术可以防止不安全的内存访问;这就是为什么你不能保护代码内部的秘密。)

【讨论】:

    【解决方案3】:

    经过研究,我找到了一个适合我的简单好用的解决方案:

    class SecurityLayer {
    
    private static var authorized: Bool = false
    
    static func makeAuthorizeCheck() -> API2? {
        if authorized {
            return API2()
        }
        return nil
       }
    }
    

    二等(非子类)

    class Secret {
    
      func test() {
        print("test")
      }
    
      fileprivate init() {
    
      }
    }
    

    示例

      SecurityLayer.makeAuthorizeCheck()?.test() //working
      Secret() //forbidden
      Secret.test() //compiler find this method, but there are no permissions to use this one
    

    Secret 中的构造函数是private 时,这将不再起作用。对我来说,fileprivate 的好处现在是显而易见的。 !类必须在一个文件中!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-09-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多