SpringBoot 与Shiro 整合系列(二)实现MD5盐值加密
SpringBoot 与Shiro 整合- 密码的比对、MD5盐值加密
目录
密码的比对、MD5盐值加密都是通过 CrendentialsMatcher(凭证匹配器)来实现的。
一、Shiro认证时的密码比对
在Shiro进行密码比对时,一定会去拿UsernamePasswordToken 和SimpleAuthenticationInfo中封装的密码信息,那么此时要调用UsernamePasswordToken的 getPassword方法,或者调用SimpleAuthenticationInfo的getCredentials方法。
1.在UserNamePasswordkToken 中的 getPassword() 方法中打上断点,往前跟踪一下即可。
2.开启debug模式,点击登录,跟踪发现
SimpleCredentialsMatcher类有一个doCredentialsMatch方法,在该方法中
就进行了密码比对工作:
/**
* This implementation acquires the {@code token}'s credentials
* (via {@link #getCredentials(AuthenticationToken) getCredentials(token)})
* and then the {@code account}'s credentials
* (via {@link #getCredentials(org.apache.shiro.authc.AuthenticationInfo) getCredentials(account)}) and then passes both of
* them to the {@link #equals(Object,Object) equals(tokenCredentials, accountCredentials)} method for equality
* comparison.
*
* @param token the {@code AuthenticationToken} submitted during the authentication attempt.
* @param info the {@code AuthenticationInfo} stored in the system matching the token principal.
* @return {@code true} if the provided token credentials are equal to the stored account credentials,
* {@code false} otherwise
*/
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
Object tokenCredentials = getCredentials(token);
Object accountCredentials = getCredentials(info);
return equals(tokenCredentials, accountCredentials);
}
3.继续跟踪,发现我们自定义Realm的父类方法调用了 CrendebtialsMatcher组件 进行密码比对
/**
* Asserts that the submitted {@code AuthenticationToken}'s credentials match the stored account
* {@code AuthenticationInfo}'s credentials, and if not, throws an {@link AuthenticationException}.
*
* @param token the submitted authentication token
* @param info the AuthenticationInfo corresponding to the given {@code token}
* @throws AuthenticationException if the token's credentials do not match the stored account credentials.
*/
protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
CredentialsMatcher cm = getCredentialsMatcher();
if (cm != null) {
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.");
}
}
总结一下也就是,密码的比对是通过AuthenticatingRealm 的 CredentialsMatcher属性的doCredentialsMatch方法来完成的。
二、加密
1.密码的加密
在数据表中存的密码不应该是123456,而应该是123456加密之后的字符串,而且还要求这个加密算法是不可逆的,即由加密后的字符串不能反推回来原来的密码,如果能反推回来那这个加密是没有意义的。
著名的加密算法,比如 MD5,SHA1
2.MD5加密
1). 如何把一个字符串加密为MD5
2). 使用MD5加密算法后,前台用户输入的字符串如何使用MD5加密,需要做的是:替换当前Realm的credentialsMatcher属性。直接使用HashedCredentialsMatcher 对象,并设置加密算法即可。
如图所示:Md5CredentialsMatcher已经过期了,提示使用HashedCredentialsMatcher
下面为 HashedCredentialsMatcher 哈希凭据匹配器 的继承关系:
3.实现MD5加密
步骤如下:
3.1在系列(一)的基础上,修改ShiroConfig配置类:
(1)添加 凭证匹配器 配置方法
/**
* 配置 凭证匹配器 (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了,
* 所以我们需要修改下doGetAuthenticationInfo中的代码;)
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//设置加密算法
hashedCredentialsMatcher.setHashAlgorithmName("MD5");
//设置加密次数,比如两次,相当于md5(md5())
hashedCredentialsMatcher.setHashIterations(1024);
return hashedCredentialsMatcher;
}
(2)修改创建Realm方法:
在方法设置 凭证匹配器
/**
* 1.创建Realm
*/
@Bean
public UserRealm userRealm(){
UserRealm userRealm = new UserRealm();
//设置 凭证匹配器
userRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return userRealm;
}
3.2 修改下自定义Realm类中doGetAuthenticationInfo方法中的代码:
返回的SimpeAuthenticationInfo对象时,需要采用SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName)构造函数来构建对象。
/**
* 执行认证逻辑
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行认证逻辑");
//编写shiro 判断逻辑,判断用户名和密码
//1.判断用户名
UsernamePasswordToken userToken=(UsernamePasswordToken) token;
//根据用户名获取用户信息
User user=userService.findUserByName(userToken.getUsername());
if(user == null){
//用户不存在
return null;//shiro底层会抛出 UNknowAccountException
}
//2.判断密码 第一个参数:需要返回给 subject.login方法的数据 第二个参数:数据库密码 第三个参数:realm的名字
// return new SimpleAuthenticationInfo(user, user.getPassword(), "");
//根据用户的情况,来构建AuthenticationInfo对像并返回,通常使用的实现类是SimpleAuthenticationInfo
//以下信息是从数据库获取的
//1).principal:认证的实体类信息。可以是username,也可以是数据表对应的用户的体类对象
Object principal=user;
//2).credentials:密码(数据库获取的用户的密码)
Object credentials=user.getPassword();
//3).realmName:当前realm对象的name,调用父类的getName()方法即可
String realmName=getName();
ByteSource credentialsSalt =null;
// //4).盐值
// ByteSource credentialsSalt = ByteSource.Util.bytes(user.getName());//盐值 要唯一
return new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);
}
3.3 修改数据库中的用户表的登录密码
查看源码,看一下shiro是怎么加密的:通过断点可以看到,实际的加密为
创建main,得到 密码明文111111的MD5加密1024次后的密文:879d6457d5315e047d842e5507c262b5
public static void main(String[] args) {
String hashAlgorithmName="MD5";//加密算法(与配置文件中的一致)
String credentials="111111";//密码
// ByteSource salt=ByteSource.Util.bytes("admin");//盐值
ByteSource salt=null;//盐值
int hashIterations=1024;//加密次数(与配置文件中的一致)
System.out.println(new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations));
}
修改到数据库中:
3.4 测试
打断点,debug方法启动项目,输入用户名 admin 密码 111111,点击登录,密码匹配如下:
发现密码相等,登录成功,进入test.html页面:
三、MD5盐值加密
1.为什么使用MD5盐值加密?
shiro用密码匹配,密码一样,就ok。如果两个用户的密码一样,则就会造成麻烦。所以使用MD5盐值加密。
2.盐值加密简单来说就是:两个一样的西红柿,加不同的盐炒出来的味道不一样。
3.什么适合作为盐值呢?肯定是惟一的东西。比如用户名(一般采用手机号,或者邮箱等等)(用户id也ok)
4.盐值获得方法:
MD5盐值加密实现:
(1)修改下自定义Realm类中doGetAuthenticationInfo方法中的代码
(2)修改数据库中的用户表的登录密码:
(3)测试
打断点,debug方法启动项目,输入用户名 admin 密码 111111,点击登录,密码匹配如下:
测试成功!!!
四、总结
1.密码的比对是 通过 AuthenticatingRealm 的 CrendentialsMatcher 属性来进行的
2.盐值最好设置为一个用户的唯一值(一般采用用户名)
3.盐值加密的注意点
- 在自定义Realm的认证方法(doAuthenticationInfo方法),返回的SimpeAuthenticationInfo对象时,需要采用SimpleAuthenticationInfo(principal,credentials, credentialsSalt, realmName)构造函数来构建对象
- 计算盐值:ByteSource对象,可以采用ByteSource.Util.byte(String UserID)获取
- 盐值需要唯一: 一般使用随机字符串或 user id
- 如何计算盐值加密后的密码的值:使用 new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);
4.CrendentialsMatcher组件(凭证匹配器)作用
(1)用来进行密码比对。
(2)用来对密码进行加密。
五、完整代码
点击此处下载
相关文章
- JavaWeb-SpringBoot_(上)腾讯云点播服务之视频的上传-demo
- SpringBoot Cglib代理不生效
- SpringBoot ( 七 ) :springboot + mybatis 多数据源最简解决方案
- intellij idea2021.2:创建一个springboot项目(springboot 2.5.4)
- springboot集成redis配置多数据源
- SpringBoot配置属性之Migration
- SpringBoot最简单服务配置
- Springboot中,如何读取配置文件中的属性
- 补习系列(19)-springboot JPA + PostGreSQL
- 基于注解SpringAOP,AfterReturning,Before,Around__springboot工程 @Around 简单的使用__SpringBoot:AOP 自定义注解实现日志管理
- SpringBoot中对输出的json按字典表排序
- 基于Springboot整合RestTemplate调用Webservice接口
- 基于Java+SpringBoot+Vue+Uniapp(有教程)前后端分离健身预约系统设计与实现
- 基于Java+SpringBoot+Vue前后端分离酒店管理系统设计与实现
- 使用shiro对数据库中的密码进行加密存储(java+springboot+shiro)