Search code examples
javamultithreadingthread-safetyshiro

Is Shiro's DefaultPasswordService thread safe?


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.


Solution

  • 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.