【问题标题】:Is Shiro's DefaultPasswordService thread safe?Shiro 的 DefaultPasswordService 线程安全吗?
【发布时间】:2015-07-02 08:11:18
【问题描述】:

我可以拥有一个DefaultPasswordService 的实例并调用它的encryptPassword() 方法而不用担心线程安全问题吗?

文档没有说明这一点。

【问题讨论】:

标签: java multithreading thread-safety shiro


【解决方案1】:

是的,你可以。您可以通过reading the source code 轻松验证这一点:

public String encryptPassword(Object plaintext) {
  Hash hash = hashPassword(plaintext);
  checkHashFormatDurability();
  return this.hashFormat.format(hash);
}

可能发生的最糟糕的事情是checkHashFormatDurability 方法多次打印警告,但encryptPassword 方法将始终按预期工作。

但是请注意,DefaultPasswordService 本身不是线程安全的,无论是在构造之后还是在调用 setter 时。因此,在多线程中使用共享实例之前,您需要担心safe publication

【讨论】:

  • 关键是该方法不会访问或更新任何可变状态,除了它为自己创建的线程限制对象。
【解决方案2】:

它是安全的。我从以下测试类生成了一个跟踪记录所有方法调用和同时访问的字段:

public class TestShiro  implements Runnable {

private static final DefaultPasswordService defaultPasswordService = new DefaultPasswordService();

protected void exec() {
/*  calling 
 * defaultPasswordService.setHashFormat(   new Shiro1CryptFormat() ); 
 * will lead to a data race
 */
    defaultPasswordService.encryptPassword( Thread.currentThread().getName());

}

private AtomicInteger threadCount = new AtomicInteger();

public void test() throws Exception
{
    for(int i = 0; i < 8 ;i++)
    {
        Thread thread = new Thread(this, "First Test Thread " + i);
        this.threadCount.incrementAndGet();
        thread.start();
    }


    while( this.threadCount.get() > 0 )
    {
        Thread.sleep(1000);
    }


    Thread.sleep(10 * 1000);
}


public void run()
{
    exec();
    threadCount.decrementAndGet();

}

public static void main(String[] args) throws Exception
{
     (new TestShiro()).test();
}
}

一个线程的 Trace 如下所示:

com.anarsoft.agent.regression.MultiThreadedOneInstanceTemplate.run
com.anarsoft.agent.regression.TestShiro.exec
org.apache.shiro.authc.credential.DefaultPasswordService.encryptPassword
org.apache.shiro.authc.credential.DefaultPasswordService.hashPassword
org.apache.shiro.authc.credential.DefaultPasswordService.createByteSource
org.apache.shiro.util.ByteSource$Util.bytes
org.apache.shiro.util.ByteSource$Util.isCompatible
org.apache.shiro.util.SimpleByteSource.<init>
org.apache.shiro.codec.CodecSupport.toBytes
org.apache.shiro.codec.CodecSupport.toBytes
org.apache.shiro.authc.credential.DefaultPasswordService.createHashRequest
org.apache.shiro.crypto.hash.SimpleHashRequest.<init>
org.apache.shiro.authc.credential.DefaultPasswordService.hashPassword
    read  from field  org.apache.shiro.authc.credential.DefaultPasswordService.hashService from object 861842890
org.apache.shiro.crypto.hash.SimpleHashRequest.<init>
org.apache.shiro.crypto.hash.DefaultHashService.computeHash
org.apache.shiro.crypto.hash.DefaultHashService.getAlgorithmName
org.apache.shiro.crypto.hash.DefaultHashService.getHashAlgorithmName
    read  from field  org.apache.shiro.crypto.hash.DefaultHashService.algorithmName from object 1033490990
org.apache.shiro.crypto.hash.DefaultHashService.getAlgorithmName
org.apache.shiro.crypto.hash.DefaultHashService.getIterations
org.apache.shiro.crypto.hash.DefaultHashService.getHashIterations
    read  from field  org.apache.shiro.crypto.hash.DefaultHashService.iterations from object 1033490990
org.apache.shiro.crypto.hash.DefaultHashService.getIterations
org.apache.shiro.crypto.hash.DefaultHashService.getPublicSalt
org.apache.shiro.crypto.hash.DefaultHashService.getPrivateSalt
    read  from field  org.apache.shiro.crypto.hash.DefaultHashService.privateSalt from object 1033490990
org.apache.shiro.crypto.hash.DefaultHashService.isGeneratePublicSalt
    read  from field  org.apache.shiro.crypto.hash.DefaultHashService.generatePublicSalt from object 1033490990
org.apache.shiro.crypto.hash.DefaultHashService.getRandomNumberGenerator
    read  from field  org.apache.shiro.crypto.hash.DefaultHashService.rng from object 1033490990
org.apache.shiro.crypto.hash.DefaultHashService.getPublicSalt
org.apache.shiro.crypto.SecureRandomNumberGenerator.nextBytes
org.apache.shiro.crypto.SecureRandomNumberGenerator.getDefaultNextBytesSize
    read  from field  org.apache.shiro.crypto.SecureRandomNumberGenerator.defaultNextBytesSize from object 520016214
org.apache.shiro.crypto.SecureRandomNumberGenerator.nextBytes
org.apache.shiro.crypto.SecureRandomNumberGenerator.nextBytes
org.apache.shiro.crypto.SecureRandomNumberGenerator.nextBytes
    read  from field  org.apache.shiro.crypto.SecureRandomNumberGenerator.secureRandom from object 520016214
org.apache.shiro.crypto.hash.DefaultHashService.getPrivateSalt
    read  from field  org.apache.shiro.crypto.hash.DefaultHashService.privateSalt from object 1033490990
org.apache.shiro.crypto.SecureRandomNumberGenerator.nextBytes
org.apache.shiro.crypto.hash.DefaultHashService.combine
org.apache.shiro.crypto.hash.SimpleHash.<init>
org.apache.shiro.util.StringUtils.hasText
org.apache.shiro.util.StringUtils.hasLength
org.apache.shiro.crypto.hash.SimpleHash.convertSaltToBytes
org.apache.shiro.crypto.hash.SimpleHash.toByteSource
org.apache.shiro.crypto.hash.SimpleHash.convertSourceToBytes
org.apache.shiro.crypto.hash.SimpleHash.toByteSource
org.apache.shiro.crypto.hash.SimpleHash.hash
org.apache.shiro.crypto.hash.SimpleHash.hash
org.apache.shiro.crypto.hash.SimpleHash.getDigest
java.util.HashMap.getNode
    read  from field  java.util.HashMap.table from object 832279283
java.util.HashMap.getNode
    read  from field  java.util.HashMap$Node.hash from object 22805895
java.util.HashMap.getNode
    read  from field  java.util.HashMap$Node.key from object 22805895
java.util.LinkedHashMap.get
    read  from field  java.util.LinkedHashMap.accessOrder from object 832279283
java.util.LinkedHashMap.get
    read  from field  java.util.HashMap$Node.value from object 22805895
java.util.HashMap.getNode
    read  from field  java.util.HashMap.table from object 1924582348
java.util.HashMap.getNode
    read  from field  java.util.HashMap$Node.hash from object 2097514481
java.util.HashMap.getNode
    read  from field  java.util.HashMap$Node.key from object 2097514481
java.util.HashMap.get
    read  from field  java.util.HashMap$Node.value from object 2097514481
org.apache.shiro.crypto.hash.SimpleHash.getDigest
org.apache.shiro.crypto.hash.SimpleHash.setIterations
org.apache.shiro.authc.credential.DefaultPasswordService.checkHashFormatDurability
org.apache.shiro.authc.credential.DefaultPasswordService.checkHashFormatDurability
    read  from field  org.apache.shiro.authc.credential.DefaultPasswordService.hashFormatWarned from object 861842890
org.apache.shiro.authc.credential.DefaultPasswordService.checkHashFormatDurability
    read  from field  org.apache.shiro.authc.credential.DefaultPasswordService.hashFormat from object 861842890
org.apache.shiro.authc.credential.DefaultPasswordService.encryptPassword
    read  from field  org.apache.shiro.authc.credential.DefaultPasswordService.hashFormat from object 861842890
org.apache.shiro.authc.credential.DefaultPasswordService.checkHashFormatDurability
org.apache.shiro.crypto.hash.format.Shiro1CryptFormat.format
org.apache.shiro.util.SimpleByteSource.toBase64
org.apache.shiro.codec.Base64.encodeToString
org.apache.shiro.codec.Base64.encode
org.apache.shiro.codec.Base64.encode
org.apache.shiro.codec.CodecSupport.toString
org.apache.shiro.codec.CodecSupport.toString
org.apache.shiro.crypto.hash.SimpleHash.toBase64
org.apache.shiro.codec.Base64.encodeToString
org.apache.shiro.codec.Base64.encode
org.apache.shiro.codec.Base64.encode
org.apache.shiro.codec.CodecSupport.toString
org.apache.shiro.codec.CodecSupport.toString
com.anarsoft.agent.regression.MultiThreadedOneInstanceTemplate.run
    read  from field  com.anarsoft.agent.regression.MultiThreadedOneInstanceTemplate.threadCount from object 1461149300

所以只有从创建 DefaultPasswordService 写入的字段中读取。在我的测试中,创建发生在主线程中。

【讨论】:

  • 你是如何获得跟踪输出的,比如“从字段中读取”等。有没有一个库可以做到这一点?
  • 我使用当前未发布的vmlens.com 新版本创建了它。我将在这周完成最后的测试,所以你可以自己尝试。
  • 我看不出这如何证明该方法是线程安全的。只有当 javadoc 说它是线程安全的,或者您可以从源代码中判断它是线程安全的,您才能确定这一点。
  • 如果您生成程序跟踪并检查所有字段访问是否可以根据 java 内存模型排序,您应该能够找到良性竞争条件。当然,您仍然会遇到由于未原子访问字段而发生竞争的问题,这实际上可能发生在方法 checkHashFormatDurability() 中。在我的测试中!(format instanceof ParsableHashFormat) && log.isWarnEnabled()) 总是返回 false,所以在这种情况下这不是问题
【解决方案3】:

它是线程安全的,因为在加密过程中没有写入类成员。

从技术上讲,您确实需要确保在执行encryptPassword(...) 期间没有对setHashServicesetHashFormat 的调用。例如,调用 setHashService(null) 会导致 encryptPassword(...) 引发 NPE。

在实践中,这可能不是问题,因为在服务启动并运行后不太可能有理由更改 HashService 或 HashFormat。

【讨论】:

    猜你喜欢
    • 2020-04-15
    • 2018-07-18
    • 2011-07-04
    • 2014-04-26
    • 2012-11-30
    • 2010-12-30
    • 2013-03-12
    • 2021-08-03
    • 2010-12-27
    相关资源
    最近更新 更多