Shiro的MD5和Salt
1. 原理
用户注册时
- 生成一段随机字符串,一般使用UUID,生成的字符串即所谓的salt
- 将用户输入的密码+salt,整体进行加密,
- 将加密后的数据存储到数据库,
- salt也应该存储到数据库中。
用户登录时
- 将用户输入的密码+salt,整体进行加密
- 与数据库中存储的数据进行比对
- 相同则为密码正确,不同则为密码错误
2. md5哈希散列
/**
* @Author: Hjx
* @Date: 2021/8/9 17:24
*/
public class TestShiroMd5 {
public static void main(String[] args) {
// 参数1:字符串的值,即需要加密的字符串
Md5Hash md5Hash = new Md5Hash("123");
System.out.println(md5Hash.toHex());
// 参数2:salt
Md5Hash md5Hash1 = new Md5Hash("123", "hjx");
System.out.println(md5Hash1);
// 参数3:散列值,即散列多少次
Md5Hash md5Hash2 = new Md5Hash("123", "hjx", 1024);
System.out.println(md5Hash2);
}
}
3. shiro添加md5
shiro中凭证信息的查验是在AuthenticatingRealm类中的assertCredentialsMatch函数实现的
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info = getCachedAuthenticationInfo(token);
if (info == null) {
//otherwise not cached, perform the lookup:
info = doGetAuthenticationInfo(token);
log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
if (token != null && info != null) {
cacheAuthenticationInfoIfPossible(token, info);
}
} else {
log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
}
if (info != null) {
// 在这里进行凭证信息的验证
assertCredentialsMatch(token, info);
} else {
log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}]. Returning null.", token);
}
return info;
}
protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
// 获取凭证适配器
CredentialsMatcher cm = getCredentialsMatcher();
if (cm != null) {
// 默认的进行 字符串的匹配 equals 比较
if (!cm.doCredentialsMatch(token, info)) {
//not successful - throw an exception to indicate this:
String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";
throw new IncorrectCredentialsException(msg);
}
} else {
throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify " +
"credentials during authentication. If you do not wish for credentials to be examined, you " +
"can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.");
}
}
所以,如果我们要使用md5加密,可以修改 凭证适配器的类型
Test测试类
public class TestMd5Authenticator {
public static void main(String[] args) {
// 创建SecurityManager安全管理器
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
// 给SecurityManager设置自定义Realm
CustomerMd5Realm customerMd5Realm = new CustomerMd5Realm();
// 设置md5 凭证适配器
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");
customerMd5Realm.setCredentialsMatcher(hashedCredentialsMatcher);
defaultSecurityManager.setRealm(customerMd5Realm);
// 给安全工具类SecurityUtils 设置 SecurityManager
SecurityUtils.setSecurityManager(defaultSecurityManager);
// 通过安全工具类SecurityUtils获取 主体subject
Subject subject = SecurityUtils.getSubject();
// 创建Token,模拟接收前端用户输入的用户名和密码
UsernamePasswordToken token = new UsernamePasswordToken("hjx", "123");
try {
System.out.println("认证状态 : "+subject.isAuthenticated());
subject.login(token);
System.out.println("认证状态 : "+subject.isAuthenticated());
}catch (UnknownAccountException e){
e.printStackTrace();
System.out.println("用户名错误");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("密码错误");
}catch (Exception e){
e.printStackTrace();
}
}
}
自定义的Realm
/**
* @Author: Hjx
* @Date: 2021/8/9 18:03
*/
public class CustomerMd5Realm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 从 token 中获取用户输入的身份凭证
String userName = (String) token.getPrincipal();
// 模拟从数据库查询出的用户名和密码
String name = "hjx";
String pass = "3edd36faca731a93a8a00be8490d2276";
if (name.equals(userName)){
/*
SimpleAuthenticationInfo 是 AuthenticationInfo 的实现类
参数1:用户名
参数2:md5加密后的密码
参数3:自定义realm的名称
*/
return new SimpleAuthenticationInfo(name, pass, this.getName());
}
return null;
}
}
4. 加盐
修改自定义Realm
/**
* @Author: Hjx
* @Date: 2021/8/9 18:03
*/
public class CustomerMd5Realm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 从 token 中获取用户输入的身份凭证
String userName = (String) token.getPrincipal();
// 模拟从数据库查询出的用户名和密码
String name = "hjx";
String pass = "3edd36faca731a93a8a00be8490d2276";
String credentialsSalt = "hjx";
if (name.equals(userName)){
/*
SimpleAuthenticationInfo 是 AuthenticationInfo 的实现类
参数1:用户名
参数2:md5加密后的密码
参数3:盐值,类型为ByteSource,需要进行格式转换
参数4:自定义realm的名称
*/
return new SimpleAuthenticationInfo(name, pass, ByteSource.Util.bytes(credentialsSalt), this.getName());
}
return null;
}
}
5. 添加散列次数
修改Test测试类
/**
* @Author: Hjx
* @Date: 2021/8/9 17:57
*/
public class TestMd5Authenticator {
public static void main(String[] args) {
// 创建SecurityManager安全管理器
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
// 给SecurityManager设置自定义Realm
CustomerMd5Realm customerMd5Realm = new CustomerMd5Realm();
// 设置md5 凭证适配器
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
// 设置加密方式
hashedCredentialsMatcher.setHashAlgorithmName("md5");
// 设置散列次数
hashedCredentialsMatcher.setHashIterations(1024);
customerMd5Realm.setCredentialsMatcher(hashedCredentialsMatcher);
defaultSecurityManager.setRealm(customerMd5Realm);
// 给安全工具类SecurityUtils 设置 SecurityManager
SecurityUtils.setSecurityManager(defaultSecurityManager);
// 通过安全工具类SecurityUtils获取 主体subject
Subject subject = SecurityUtils.getSubject();
// 创建Token,模拟接收前端用户输入的用户名和密码
UsernamePasswordToken token = new UsernamePasswordToken("hjx", "123");
try {
System.out.println("认证状态 : "+subject.isAuthenticated());
subject.login(token);
System.out.println("认证状态 : "+subject.isAuthenticated());
}catch (UnknownAccountException e){
e.printStackTrace();
System.out.println("用户名错误");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("密码错误");
}catch (Exception e){
e.printStackTrace();
}
}
}
代码及资料地址:hjx: 知道的越多,越发觉自己的无知 (gitee.com)
视频学习参考:【编程不良人】2020最新版Shiro教程,整合SpringBoot项目实战教程_哔哩哔哩_bilibili