如何使Apache Shiro凭证匹配器与自定义加盐密码哈希结果匹配?
诊断与解决方案
我一眼就看出问题出在盐值的处理方式不一致上——你在数据库里存的是Base64编码的盐字符串,但Shiro并没有自动解码这个字符串,而是直接把字符串本身的字节当作盐来计算哈希,这就导致了Shiro生成的哈希和你自定义代码生成的完全不匹配。
问题细节拆解
你的自定义代码逻辑是:
- 生成16字节的随机盐数组
- 将盐数组用Base64编码成字符串存入数据库
- 用原始盐数组对密码进行SHA-256哈希,再把哈希结果Base64编码存入数据库
但Shiro这边的逻辑是:
- 从数据库查询到盐的Base64字符串后,默认会直接把这个字符串的UTF-8字节当作盐值(比如你存的
bigG9fGM9mjcqNiprFQ4+g==,Shiro会把这个字符串的每个字符转换成字节,而不是解码Base64得到原始的16字节数组) - 用错误的盐值计算哈希,自然和你预先存储的哈希结果不匹配,导致登录失败
两种可行的解决方案
方案1:数据库存储原始盐字节(推荐)
修改你的数据库users表的salt字段类型为二进制类型(比如MySQL的VARBINARY(16),因为你的盐是16字节),然后在生成盐的时候直接存入原始字节数组,而不是Base64编码后的字符串。
调整你的Password类的saveHashedPassword方法,把saltBytes直接存入数据库(而不是Base64编码后的字符串),这样Shiro查询到的就是原始的盐字节数组,和你的哈希逻辑完全匹配。
方案2:自定义CredentialsMatcher解码Base64盐值
如果你不想修改数据库结构,可以自定义一个继承自HashedCredentialsMatcher的类,重写获取盐值的逻辑,将查询到的Base64字符串解码成原始字节数组:
import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.util.ByteSource; import java.util.Base64; public class Base64SaltHashedCredentialsMatcher extends HashedCredentialsMatcher { @Override protected Object getSalt(Object credentials) { // 这里假设传入的salt是Base64字符串 if (credentials instanceof String) { byte[] saltBytes = Base64.getDecoder().decode((String) credentials); return ByteSource.Util.bytes(saltBytes); } return super.getSalt(credentials); } }
然后修改你的shiro.ini配置,替换原来的credentialsMatcher:
credentialsMatcher = com.yourpackage.Base64SaltHashedCredentialsMatcher credentialsMatcher.hashAlgorithmName = SHA-256 credentialsMatcher.storedCredentialsHexEncoded = false credentialsMatcher.hashSalted = true # 如果需要迭代次数,这里可以设置,比如 credentialsMatcher.hashIterations = 1024
额外验证点
另外,你可以验证一下Shiro的哈希逻辑和你的代码是否一致:用Shiro的SimpleHash类来生成哈希,对比你的自定义代码结果:
import org.apache.shiro.crypto.hash.SimpleHash; import org.apache.shiro.util.ByteSource; import java.util.Base64; public class TestHash { public static void main(String[] args) { String clearTextPassword = "secret"; String saltBase64 = "bigG9fGM9mjcqNiprFQ4+g=="; byte[] saltBytes = Base64.getDecoder().decode(saltBase64); // Shiro的哈希方式 SimpleHash hash = new SimpleHash("SHA-256", clearTextPassword, ByteSource.Util.bytes(saltBytes), 1); String shiroHashedPassword = hash.toBase64(); // 对比你生成的哈希值 mKYfzHXt4kHoLgzc7/C2upKXuCZ0SSGjUrnGIqzd92E= System.out.println(shiroHashedPassword); } }
如果输出和你生成的哈希值一致,说明调整盐值处理后就能解决问题。
内容的提问来源于stack exchange,提问作者epilot6ke
相关产品推荐
相关产品推荐

