用于基本身份验证的 Apache Shiro 凭据匹配总是失败

Apache Shiro credentials matching for basic authentication always fails

我正在尝试使用 Apache Shiro 实现基本身份验证。我将侦听器和过滤器添加到我的 web.xml 文件中:

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

并将 authcBasic 过滤器应用于我的 shiro.ini 文件中的受保护页面:

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

此外,我正在使用 Sha256CredentialsMatcher 和 JdbcRealm subclass 作为领域:

[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

领域class:

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

可以通过将 JSON 对象发布到 RESTful 网络服务来将用户添加到数据库中:

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());
    }
}

(这几乎是在 tutorial from the Shiro web page 之后。)

这种新用户注册工作正常,当客户端尝试进行身份验证时,用户名和密码可以毫无问题地到达服务器。但是之后总是登录失败

我已经通过调试找到了以下几点: Sha256CredentialsMatcher 从数据库中检索与接收到的用户名相匹配的盐,并使用该盐对接收到的密码进行哈希处理。之后,它将结果与已存储在该用户名的数据库中的密码哈希进行比较。只是在我的例子中,即使客户端发送了正确的用户名和密码组合,这些哈希值也永远不会匹配。似乎盐 and/or 密码哈希在将它们存储在数据库中并再次检索它们的过程中发生了变化。或者好像 Sha256Hash(...) 构造函数和 Sha256CredentialsMatcher.doCredentialsMatch(...) 方法在散列相同的密码时得到不同的结果。但我不知道这怎么会发生。

我已经尝试将盐存储在数据库中作为 salt.toString() 而不是 salt.toBase64():

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

两种变体都会出现错误。

我使用以下并且有效

credentialsMatcher = org.apache.shiro.authc.credential.HashedCredentialsMatcher
credentialsMatcher.hashAlgorithmName=SHA-256
credentialsMatcher.storedCredentialsHexEncoded = false
credentialsMatcher.hashIterations = 1024
credentialsMatcher.hashSalted = true

您的实施中缺少 credentialsMatcher.**hashSalted = true** 部分。

将近三个月后,我找到了解决方案。 :-/ 与 Shiro 文档中的教程相反,您不能简单地实例化一个 ByteSource 对象并将其用作盐(至少不能使用 1.2.3 版)。相反,您需要从 ByteSource 对象获取一个 String 并将其用作盐。我选择获取一个 Base64 编码的字符串,所以我的 encryptPassword 方法现在看起来像这样:

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);
}