【问题标题】:Dependency Injection ASP.NET : Unaware about the dependencies until Runtime依赖注入 ASP.NET:直到运行时才知道依赖关系
【发布时间】:2016-11-10 15:33:38
【问题描述】:

我正在使用 ASP.NET Core 并使用 DI 来构建散列功能。因为我不知道现阶段使用的散列类型(我们将其存储在持久存储中)。

ICrypto合约

public interface ICrypto
{
    string HashPassword(string plainPassword);
    bool VerifyHashedPassword(string hashedPassword, string providedPassword);     
}

我有几个 ICrypto 的实现,它们只是包装其他库并提供 ICrypto 签名的实现。例如:

  • CryptoMD5
  • CryptoSHA1
  • CytpoOther

现在,在 UserService 中,我注入 ICyrpto 以散列密码,例如:

Public class UserService
{
     ICrypto _crypto;

     public UserService(ICrypto crypto)
     {
         _crypto = crypto;
     }

    public bool Login (string username, string password)
    {   
        //code omitted
         var hash = _crypto.HashPassword(password);
    }
}

在启动类中为容器添加依赖

//get encryption type stored in cache, db or somewhere
   var cryptoType = //get param
   if (cryptoType  = "SHA1")
   {
       services.AddTransient<ICrypto, CryptoSHA1>();
   }
   else if (cryptoType  = "MD5")
   {
       services.AddTransient<ICrypto, CryptoMD5>();
   }

我正在寻找一种根据最佳实践执行此操作的方法,并将反映 Steves 提到的内容。

【问题讨论】:

  • 工厂不违反 DI,您确实误读了 Stevens 的回答。您使用的是静态工厂,这违反了 IoC/DI 的解耦性质。如果你注入你的工厂,它就完美了
  • @h.salman,在我看来,你做错了。您在您的服务中注入 ICrypto。这意味着您想要注入一个可以加密/解密您的数据的对象,而不管算法如何。否则,您将直接注入 ICryptoSHA1 或 ICryptoMD5。所以,或者你注入一个可以加密/解密许多算法的 ICrypto,或者你定义你需要的类型,然后注入正确的。
  • @FabricioKoch,请通过回答问题并说明如何实现这一点来说明这一点。
  • @Tseng 不完全是。正如我所解释的here,工厂抽象经常违反依赖倒置原则,这是依赖注入背后的驱动力。
  • 我将使用Crypt(string text, string algorithm)Decrypt(string text, string algorithm) 方法创建一个ICrypto。

标签: c# design-patterns dependency-injection asp.net-core inversion-of-control


【解决方案1】:

如果您从数据库中获得的 cryptoType 值在应用程序的生命周期内保持不变(这意味着,如果您想更改它,您可以重新启动应用程序),这意味着 cryptoType 是一个配置值,您可以按照您的描述简单地连接您的应用程序:

var cryptoType = //get param
if (cryptoType = "SHA1")
{
    services.AddTransient<ICrypto, CryptoSHA1>();
}
else if (cryptoType = "MD5")
{
    services.AddTransient<ICrypto, CryptoMD5>();
}

但是,如果您需要动态交换实现(在您的特定情况下我发现这不太可能,但为了论证的目的,我们假设),解决方案是实现代理并包装真正的实现。示例:

public interface DatabaseCryptoSelectorProxy : ICrypto
{
    private readonly CryptoSHA1 sha;
    private readonly CryptoMD5 md5;

    public DatabaseCryptoSelectorProxy(CryptoSHA1 sha, CryptoMD5 md5) {
        this.sha = sha;
        this.md5 = md5;
    }

    public string HashPassword(string plainPassword) =>
        GetCrypto().HashPasswords(plainPassword);

    public bool VerifyHashedPassword(string hashedPassword, string providedPassword) =>
        GetCrypto().VerifyHashedPassword(hashedPassword, providedPassword);

    private ICrypto GetCrypto() {
        var cryptoType = // get param
        if (cryptoType = "SHA1") return this.sha;
        if (cryptoType = "MD5") return this.md5;
        throw new InvalidOperationException("Unknown cryptotype: " + cryptotype);
    }
}

这个代理有几个明显的优势:

  • 它使ICrypto 的消费者忘记了一些复杂的调度是基于数据库中的一些数据进行的。
  • 它可以避免在对象图构建期间必须执行此查询,因为这会使此过程不可靠且难以验证。

从安全角度来看,关于您的密码散列设计的一些注意事项。我看不出有任何充分的理由以您正在做的方式从加密方法切换,尤其是切换到像 MD5 这样的算法。相反,我建议以Rfc2898DeriveBytes 的形式使用PBKDF2。 here 显示了如何执行此操作的示例。通过将哈希迭代次数连接到哈希密码(例如,只需执行+ "|" + iterations),您可以稍后通过跟上行业标准来增加使用的迭代次数,并且它允许您自动重新哈希用户的密码如果您检测到使用的迭代次数是旧值,则登录如果他的号码。

此外,如果您认为您需要离开 PBKDF2,您可以在哈希前加上使用的算法,这样您就可以再次使用代理,根据算法将哈希密码传递给正确的实现-字首。通过将算法存储在数据库中的密码哈希中,您可以透明地迁移,而无需一次转换所有现有密码(这是不可能的,因为您无法解密哈希密码)。

【讨论】:

  • 谢谢@Steven 你太棒了。在您使用代理的方法中,不需要传递容器的依赖项,因为它在运行时已经这样做了,这是它的优势。如果我错了,请纠正我。
  • @h.salman 您可以通过让代理创建其依赖项来使其“自我维持”。这可能会在您的加密用例中很好地工作,其中真正的实现可能没有自己的依赖关系并且是线程安全的。通常情况下,这些条件不成立,最好让容器控制对象图。这样可以验证和诊断图形,并更容易替换、装饰或拦截组件。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-01
  • 2012-11-23
  • 2021-11-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多