Shiro 的 DefaultPasswordService 线程安全吗?
Is Shiro's DefaultPasswordService thread safe?
我可以拥有 DefaultPasswordService
的单个实例并调用它的 encryptPassword()
方法而不用担心线程安全问题吗?
文档没有说明这一点。
是的,你可以。您可以通过 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。
很安全。我从以下测试 class:
生成了一个跟踪记录所有方法调用和并发访问的字段
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 写入的字段。在我的测试中,创建发生在主线程中。
它是线程安全的,因为在加密过程中没有写入 class 成员。
从技术上讲,您确实需要确保在执行 encryptPassword(...)
期间没有调用 setHashService
或 setHashFormat
。例如,调用 setHashService(null)
会导致 encryptPassword(...)
引发 NPE。
在实践中,这可能不是问题,因为不太可能有理由在服务启动后更改 HashService 或 HashFormat 运行。
我可以拥有 DefaultPasswordService
的单个实例并调用它的 encryptPassword()
方法而不用担心线程安全问题吗?
文档没有说明这一点。
是的,你可以。您可以通过 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。
很安全。我从以下测试 class:
生成了一个跟踪记录所有方法调用和并发访问的字段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 写入的字段。在我的测试中,创建发生在主线程中。
它是线程安全的,因为在加密过程中没有写入 class 成员。
从技术上讲,您确实需要确保在执行 encryptPassword(...)
期间没有调用 setHashService
或 setHashFormat
。例如,调用 setHashService(null)
会导致 encryptPassword(...)
引发 NPE。
在实践中,这可能不是问题,因为不太可能有理由在服务启动后更改 HashService 或 HashFormat 运行。