md5加密
在涉及密碼存儲問題上,應該進行加密存儲,而不能是明文,否則賬號密碼泄露,就會產生意想不到的後果。所以,通常都會採用非對稱加密,什麼是非對稱呢,就是不可逆的,而md5就是這樣一個算法。
如123456用md5加密後,得到字符串:e10adc3949ba59abbe56e057f20f883e
String password = "123456";
String encodedPassword = new Md5Hash(password).toString();
System.out.println(encodedPassword);
鹽
上面講了md5加密,但md5加密也有一些缺陷:如果我的密碼是123456,你的也是123456,那麼md5的值是一樣的,通過比較加密後的字符串,就可以反推過來。
雖然md5不可逆,但可以用窮舉法,把一些常用的密碼的md5值記錄下來,比如123,abcde等等,這樣也可以破解一部分。
爲了解決這個問題,引入了鹽(salt)的概念,雖然每次123456的md5值都是e10adc3949ba59abbe56e057f20f883e
,但是加上鹽之後,即123456+隨機數,那麼md5值就不一樣了,這個隨機數 就是鹽,而這個隨機數也會在數據庫裏保存下來,每個不同的用戶,隨機數也是不一樣的。再者就是加密次數,可以加密一次 也可以加密多次,加密次數越多,密文的安全性也就越高。
String password = "123456";
String salt = new SecureRandomNumberGenerator().nextBytes().toString(); //隨機數:鹽
int times = 2; //加密次數
String algorithmName = "md5"; //散列算法
String encodedPassword = new SimpleHash(algorithmName,password,salt,times).toString();
System.out.println("密文:" + encodedPassword);
獲取密文和鹽
public class PasswordHelper {
public static void main(String[] args) {
String algorithmName = "md5";
String username = "itmyhome";
String password = "123456";
String salt1 = password;
String salt2 = new SecureRandomNumberGenerator().nextBytes().toHex();
int hashIterations = 2;
SimpleHash hash = new SimpleHash(algorithmName, password,
salt1 + salt2, hashIterations);
String encodedPassword = hash.toHex();
//每次輸出結果都不一樣
System.out.println("密文:" + encodedPassword); //b4b88b2ef28089f9c802df11bd216a33
System.out.println("鹽:" + salt2); //713183f112f48824613f0f05d0d10060
}
}
修改Realm
public class UserRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
// TODO Auto-generated method stub
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
// TODO Auto-generated method stub
//獲取賬號密碼
UsernamePasswordToken t = (UsernamePasswordToken) token;
String password = new String(t.getPassword());
String username = (String) token.getPrincipal();
//獲取數據庫種的密碼、鹽(此處寫死,測試用)
User user = new User();
user.setUsername(username);
user.setPassword("b4b88b2ef28089f9c802df11bd216a33");
user.setSalt("713183f112f48824613f0f05d0d10060");
if (!"itmyhome".equals(username)) {
// 拋出 帳號找不到異常
throw new UnknownAccountException();
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
user.getUsername(), // 用戶名
user.getPassword(), // 密碼
ByteSource.Util.bytes(password + user.getSalt()),// salt=password+salt
getName() // realm name
);
return authenticationInfo;
}
}
ini配置
[main]
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
credentialsMatcher.hashAlgorithmName=md5
credentialsMatcher.hashIterations=2
credentialsMatcher.storedCredentialsHexEncoded=true
userRealm=com.itmyhome.UserRealm
securityManager.realms=$userRealm
#這行很重要
userRealm.credentialsMatcher=$credentialsMatcher
通過credentialsMatcher.hashAlgorithmName=md5指定散列算法爲md5,需要和生成密碼時的一樣
credentialsMatcher.hashIterations=2,散列迭代次數
credentialsMatcher.storedCredentialsHexEncoded=true表示是否存儲散列後的密碼爲16進制
此處最需要注意的就是HashedCredentialsMatcher的算法需要和生成密碼時的算法一樣。
另外HashedCredentialsMatcher會自動根據AuthenticationInfo的類型是否是SaltedAuthenticationInfo來獲取credentialsSalt鹽。
測試類
public class ShiroTest {
private static Logger logger = LoggerFactory.getLogger(ShiroTest.class);
public static void main(String[] args) {
//獲取SecurityManager工廠,此處使用Ini配置文件初始化SecurityManager
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:auth.ini");
//得到SecurityManager實例 並綁定給SecurityUtils
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
//得到Subject及創建用戶名/密碼身份驗證Token(即用戶身份/憑證)
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("itmyhome","123456");
try{
//登錄,即身份驗證
subject.login(token);
}catch(org.apache.shiro.authc.AuthenticationException e){
//身份驗證失敗
e.printStackTrace();
}
System.out.println(subject.getPrincipal());
System.out.println("User is authenticated: " + subject.isAuthenticated());
}
}