Can I have a single instance of DefaultPasswordService
and call its encryptPassword()
method without worrying about thread safety issues?
The documentation doesn't make this clear.
Can I have a single instance of DefaultPasswordService
and call its encryptPassword()
method without worrying about thread safety issues?
The documentation doesn't make this clear.
Yes, you can. You can easily verify this by reading the source code:
public String encryptPassword(Object plaintext) {
Hash hash = hashPassword(plaintext);
checkHashFormatDurability();
return this.hashFormat.format(hash);
}
The worst thing that can happen is that the checkHashFormatDurability
method prints a warning multiple times, but the encryptPassword
method will always work as expected.
Note however, that the DefaultPasswordService
is itself not thread-safe, neither after construction or when calling a setter. Therefore, you need to worry about safe publication of the shared instance before using it in multiple threads.
Its safe. I generated a trace logging all method calls and concurrently accessed fields from the following test 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();
}
}
The Trace for one thread looks like this:
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
So there are only reads, from fields written by creation of DefaultPasswordService. In my test the creation happens in the main thread.
It is thread safe in that there are no writes to class members during the encryption process.
Technically, you do need to ensure that there are no calls to setHashService
or setHashFormat
during the execution of encryptPassword(...)
. For example, a call to setHashService(null)
would cause encryptPassword(...)
to raise a NPE.
In practice, that is probably not an issue, as it is unlikely that there would be a reason to change the HashService, or HashFormat, after the service is up and running.