Search code examples
javaauthenticationshiro

Apache Shiro credentials matching for basic authentication always fails


I am trying to implement basic authentication with Apache Shiro. I added the listener and filter to my web.xml file:

<listener>
 <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
<filter>
 <filter-name>ShiroFilter</filter-name>
 <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>
<filter-mapping>
 <filter-name>ShiroFilter</filter-name>
 <url-pattern>/*</url-pattern>
 <dispatcher>REQUEST</dispatcher>
 <dispatcher>FORWARD</dispatcher>
 <dispatcher>INCLUDE</dispatcher>
 <dispatcher>ERROR</dispatcher>
</filter-mapping>

and applied the authcBasic filter to the protected pages in my shiro.ini file:

[urls]
/api/users = anon
/login.html = anon
/** = authcBasic

Also, I'm using an Sha256CredentialsMatcher and a JdbcRealm subclass as the realm:

[main]
jdbcRealm = my.package.SaltColumnJDBCRealm
sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
sha256Matcher.storedCredentialsHexEncoded = false
sha256Matcher.hashIterations = 1024 
jdbcRealm.credentialsMatcher = $sha256Matcher
jdbcRealm.authenticationQuery = SELECT password, salt FROM User WHERE email = ?
builtInCacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManager
securityManager.cacheManager = $builtInCacheManager
securityManager.realms = $jdbcRealm

Realm class:

public class SaltColumnJDBCRealm extends org.apache.shiro.realm.jdbc.JdbcRealm
{
    public SaltColumnJDBCRealm()
    {
        this.setSaltStyle(SaltStyle.COLUMN);
    }
}

Users can be added to the database by POSTing a JSON object to a RESTful webservice:

import javax.ejb.EJB;
import javax.enterprise.context.RequestScoped;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import org.apache.shiro.crypto.RandomNumberGenerator;
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.apache.shiro.util.ByteSource;

@Path("/users")
@RequestScoped
public class UserService
{
    @EJB
    UserDao userDao;

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public void register(User user)
    {
        try
        {
            encryptPassword(user);
            userDao.persist(user);
        }
        catch(InvalidEntityException | UnsupportedEncodingException e)
        {
            throw new WebApplicationException(e);
        }
    }

    private static void encryptPassword(User user) throws UnsupportedEncodingException
    {
        RandomNumberGenerator rng = new SecureRandomNumberGenerator();
        ByteSource salt = rng.nextBytes();

        String hashedPasswordBase64 = new Sha256Hash(user.getPassword(), salt, 1024).toBase64();
        user.setPassword(hashedPasswordBase64);
        user.setSalt(salt.toBase64());
    }
}

(This is pretty much following the tutorial from the Shiro web page.)

This registration of new users works fine, and when a client tries to authenticate, the user name and password reach the server without issues. But then the login always fails.

I have already tracked down the issue by debugging to the following point: The Sha256CredentialsMatcher retrieves the salt that matches the received user name from the database and hashes the received password utilizing this salt. Afterwards it compares the result with the password hash already stored in the database for this user name. It's just that in my case these hashes never match, even when a correct combination of user name and password was sent by the client. It appears as if the salt and/or the password hash get changed during the process of storing them in the database and retrieving them again. Or as if somehow the Sha256Hash(...) constructor and the Sha256CredentialsMatcher.doCredentialsMatch(...) method arrive at different results when hashing the same password. But I'm out of ideas how this might happen.

I have already tried storing the salt in the database as salt.toString() instead of salt.toBase64():

public class UserService
{
    ... 
    private static void encryptPassword(User user) throws UnsupportedEncodingException
    {
        ... 
        user.setSalt(salt.toString());
...

The error happens with both variants.


Solution

  • After almost three months I found the solution. :-/ In contrast to the tutorial from the Shiro documentation, you cannot simply instantiate a ByteSource object and use it as the salt (at least not with version 1.2.3). Instead, you need to obtain a String from the ByteSource object and use this as the salt. I chose to obtain a Base64-encoded String, so my encryptPassword method now looks like this:

    private static void encryptPassword(User user) throws UnsupportedEncodingException
    {
        RandomNumberGenerator rng = new SecureRandomNumberGenerator();
        ByteSource byteSource = rng.nextBytes();
        String salt = byteSource.toBase64();
        String hashedPasswordBase64 = new Sha256Hash(user.getPassword(), salt, 1024).toBase64();
        user.setPassword(hashedPasswordBase64);
        user.setSalt(salt);
    }