Spring Boot 整合 shiro 之盐值加密认证详解
概述
上一篇博客我们主要讲了自定义 Realm
,里面的用户认证所使用的密码
都是明文
,这种方式是不可取的,往往我们在实战开发中用户的密码都是以密文形势进行存储
,并且要求加密算法是不可逆的
,著名的加密算法有MD5、SHA1
等。所以这篇博客我们主要介绍 Shiro
安全框架学习它的加密方案
。这里我们采用md5
的方式加密,然后呢。md5加密又分为加盐,和不加盐
。
不加盐认证
@Test
public void testMatcher() {
CustomRealm customRealm = new CustomRealm();
//1,构建SecurityManager环境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
//设置Realm
defaultSecurityManager.setRealm(customRealm);
SecurityUtils.setSecurityManager(defaultSecurityManager);
//获取主体
Subject subject = SecurityUtils.getSubject();
//用户名和密码的token
UsernamePasswordToken token = new UsernamePasswordToken("dev", "123456");
try {
//2,主体提交认证请求
subject.login(token);
System.out.println("是否权限认证:isAuthenticated=" + subject.isAuthenticated());
//检查是否有角色
subject.checkRoles("admin");
System.out.println("有admin角色");
//检查是否有权限
subject.checkPermissions("user:delete", "user:edit", "user:list", "user:add");
System.out.println("有 user:add、user:list、user:edit、user:delete权限");
} catch (IncorrectCredentialsException exception) {
System.out.println("用户名或密码错误");
} catch (LockedAccountException exception) {
System.out.println("账号已被锁定");
} catch (DisabledAccountException exception) {
System.out.println("账号已被禁用");
} catch (UnknownAccountException exception) {
System.out.println("用户不存在");
} catch (UnauthorizedException ae) {
System.out.println("用户没有权限");
}
}
CustomRealm
请参考Spring Boot 使用自定义 Realm 进行认证授权(五)
加入密码认证核心代码
//进行加密
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
//采用md5加密
matcher.setHashAlgorithmName("md5");
//设置加密次数
matcher.setHashIterations(2);
customRealm.setCredentialsMatcher(matcher);
修改 CustomRealm 新增获取密文的方法
/**
* 获得密文密码
* @Author: djy
* @UpdateUser:
* @Version: 0.0.1
* @param currentPassword
* @return java.lang.String
* @throws
*/
private String getPasswordMatcher(String currentPassword){
return new Md5Hash(currentPassword, null,2).toString();
}
修改 doGetAuthenticationInfo
/**
* 用户认证
*
* @param authenticationToken
* @return org.apache.shiro.authc.AuthenticationInfo
* @throws
* @Author: djy
* @UpdateUser:
* @Version: 0.0.1
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("开始执行*******doGetAuthenticationInfo方法啦****");
//获取登录用户名
String userName = (String) authenticationToken.getPrincipal();
//通过用户名到数据库获取用户信息
String password = getPasswordByUsername(userName);
if (null == password) {
return null;
}
System.out.println("密码为:" + password);
String matcherPwd = getPasswordMatcher(password);
System.out.println(matcherPwd);
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userName, password, getName());
return authenticationInfo;
}
当用户 admin 登录进来的时候 程序输出
UsernamePasswordToken token = new UsernamePasswordToken("admin", "123456");
- 程序输出
是否权限认证:isAuthenticated=true
开始执行*******doGetAuthorizationInfo方法啦****
admin
有admin角色
开始执行*******doGetAuthorizationInfo方法啦****
admin
开始执行*******doGetAuthorizationInfo方法啦****
admin
开始执行*******doGetAuthorizationInfo方法啦****
admin
开始执行*******doGetAuthorizationInfo方法啦****
admin
有 user:add、user:list、user:edit、user:delete权限
- 假如改成还是用明文检验
// String matcherPwd=getPasswordMatcher(password);
// System.out.println(matcherPwd);
// SimpleAuthenticationInfo authenticationInfo = new
//SimpleAuthenticationInfo(username,matcherPwd,getName());
SimpleAuthenticationInfo authenticationInfo = new
SimpleAuthenticationInfo(username,password,getName());
- 程序输出
开始执行*******doGetAuthenticationInfo方法啦****
密码为:123456
4280d89a5a03f812751f504cc10ee8a5
用户名或密码错误
加盐认证
当两个用户的密码相同时,单纯使用不加盐的MD5加密方式,会发现数据库中存在相同结构的密码,这样也是不安全的。我们希望即便是两个人的原始密码一样,加密后的结果也不一样。如何做到呢?其实就好像炒菜一样,两道一样的鱼香肉丝,加的盐不一样,炒出来的味道就不一样。MD5加密也是一样,需要进行盐值加密。
创建 testSaltMatcher 方法
@Test
public void testSaltMatcher() {
CustomRealm customRealm = new CustomRealm();
//进行加密
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
//采用mdf加密
matcher.setHashAlgorithmName("md5");
//设置加密次数
matcher.setHashIterations(1);
customRealm.setCredentialsMatcher(matcher);
//1,构建SecurityManager环境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
//设置Realm
defaultSecurityManager.setRealm(customRealm);
SecurityUtils.setSecurityManager(defaultSecurityManager);
//获取主体
Subject subject = SecurityUtils.getSubject();
//用户名和密码的token
UsernamePasswordToken token = new UsernamePasswordToken("admin", "123456");
try {
//2,主体提交认证请求
subject.login(token);
System.out.println("是否权限认证:isAuthenticated=" + subject.isAuthenticated());
//检查是否有角色
subject.checkRoles("admin");
System.out.println("有admin角色");
//检查是否有权限
subject.checkPermissions("user:delete", "user:edit", "user:list", "user:add");
System.out.println("有 user:add、user:list、user:edit、user:delete权限");
//if no exception, that's it, we're done!
} catch (IncorrectCredentialsException exception) {
System.out.println("用户名或密码错误");
} catch (LockedAccountException exception) {
System.out.println("账号已被锁定");
} catch (DisabledAccountException exception) {
System.out.println("账号已被禁用");
} catch (UnknownAccountException exception) {
System.out.println("用户不存在");
} catch (UnauthorizedException ae) {
System.out.println("用户没有权限");
}
}
加盐验证核心代码
//设置盐的值
authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(username));
修改 CustomRealm 新增获取加盐密文的方法
/**
* 获得密文密码
*
* @param currentPassword
* @return java.lang.String
* @throws
* @Author: djy
* @UpdateUser:
* @Version: 0.0.1
*/
private String getPasswordMatcher(String currentPassword, String username) {
return new Md5Hash(currentPassword, username).toString();
}
修改 doGetAuthenticationInfo
/**
* 用户认证
*
* @param authenticationToken
* @return org.apache.shiro.authc.AuthenticationInfo
* @throws
* @Author: djy
* @UpdateUser:
* @Version: 0.0.1
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("开始执行*******doGetAuthenticationInfo方法啦****");
//获取登录用户名
String userName = (String) authenticationToken.getPrincipal();
//通过用户名到数据库获取用户信息
String password = getPasswordByUsername(userName);
if (null == password) {
return null;
}
// System.out.println("密码为:" + password);
// String matcherPwd = getPasswordMatcher(password);
String matcherPwd = getPasswordMatcher(password,userName);
System.out.println(matcherPwd);
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userName, matcherPwd, getName());
//设置盐的值
authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(userName));
return authenticationInfo;
}
测试
- 当用户 admin 登录进来 程序输出
UsernamePasswordToken token = new UsernamePasswordToken("admin", "123456");
开始执行*******doGetAuthenticationInfo方法啦****
a66abb5684c45962d887564f08346e8d
是否权限认证:isAuthenticated=true
开始执行*******doGetAuthorizationInfo方法啦****
admin
有admin角色
开始执行*******doGetAuthorizationInfo方法啦****
admin
开始执行*******doGetAuthorizationInfo方法啦****
admin
开始执行*******doGetAuthorizationInfo方法啦****
admin
开始执行*******doGetAuthorizationInfo方法啦****
admin
有 user:add、user:list、user:edit、user:delete权限
- 当用户 admin 登录但是密码却输成1234567 程序输出
UsernamePasswordToken token = new UsernamePasswordToken("admin", "1234567");
开始执行*******doGetAuthenticationInfo方法啦****
a66abb5684c45962d887564f08346e8d
用户名或密码错误
- 当用户 admin 登录进来密码也输入正确了但是 验证的时候改用明文 程序输出
//把下面两个方法注释掉
//String matcherPwd = getPasswordMatcher(password,userName);
//System.out.println(matcherPwd);
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username,password,getName());
开始执行*******doGetAuthenticationInfo方法啦****
用户名或密码错误
shiro 加密就讲到这里,其实有的小伙伴会有疑问,假如我们公司用sessionID 作为登录状态的凭证那么我们该怎么办呢,怎么才能让用户通过认证呢?这里我们可以自定义密码匹配类继承 HashedCredentialsMatcher类 重写它的 doCredentialsMatch (密码匹配的方法)即可。后面我们讲解实战开发的时候会详细的讲解。